diff --git a/package-lock.json b/package-lock.json
index df342d75..4cabe89b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -970,6 +970,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
+ "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
+ "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
+ "dependencies": {
+ "@floating-ui/core": "^0.7.3"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
+ "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
+ "dependencies": {
+ "@floating-ui/dom": "^0.5.3",
+ "use-isomorphic-layout-effect": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
@@ -1989,6 +2015,365 @@
"node": ">= 8"
}
},
+ "node_modules/@radix-ui/colors": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-0.1.8.tgz",
+ "integrity": "sha512-jwRMXYwC0hUo0mv6wGpuw254Pd9p/R6Td5xsRpOmaWkUHlooNWqVcadgyzlRumMq3xfOTXwJReU0Jv+EIy4Jbw=="
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
+ "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
+ "integrity": "sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-primitive": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.0.tgz",
+ "integrity": "sha512-8i1pf5dKjnq90Z8udnnXKzdCEV3/FYrfw0n/b6NvB6piXEn3fO1bOh7HBcpG8XrnIXzxlYu2oCcR38QpyLS/mg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-slot": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
+ "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
+ "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
+ "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
+ "integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "@radix-ui/react-use-escape-keydown": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-1.0.0.tgz",
+ "integrity": "sha512-Ptben3TxPWrZLbInO7zjAK73kmjYuStsxfg6ujgt+EywJyREoibhZYnsSNqC+UiOtl4PdW/MOHhxVDtew5fouQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-menu": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-controllable-state": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
+ "integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
+ "integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-icons": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.1.1.tgz",
+ "integrity": "sha512-xc3wQC59rsFylVbSusQCrrM+6695ppF730Q6yqzhRdqDcRNWIm2R6ngpzBoSOQMcwnq4p805F+Gr7xo4fmtN1A==",
+ "peerDependencies": {
+ "react": "^16.x || ^17.x || ^18.x"
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
+ "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-1.0.0.tgz",
+ "integrity": "sha512-icW4C64T6nHh3Z4Q1fxO1RlSShouFF4UpUmPV8FLaJZfphDljannKErDuALDx4ClRLihAPZ9i+PrLNPoWS2DMA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-collection": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-direction": "1.0.0",
+ "@radix-ui/react-dismissable-layer": "1.0.0",
+ "@radix-ui/react-focus-guards": "1.0.0",
+ "@radix-ui/react-focus-scope": "1.0.0",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-popper": "1.0.0",
+ "@radix-ui/react-portal": "1.0.0",
+ "@radix-ui/react-presence": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-roving-focus": "1.0.0",
+ "@radix-ui/react-slot": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.5.4"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.0.tgz",
+ "integrity": "sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@floating-ui/react-dom": "0.7.2",
+ "@radix-ui/react-arrow": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0",
+ "@radix-ui/react-use-rect": "1.0.0",
+ "@radix-ui/react-use-size": "1.0.0",
+ "@radix-ui/rect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
+ "integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-primitive": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
+ "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
+ "integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-slot": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.0.tgz",
+ "integrity": "sha512-lHvO4MhvoWpeNbiJAoyDsEtbKqP2jkkdwsMVJ3kfqbkC71J/aXE6Th6gkZA1xHEqSku+t+UgoDjvE7Z3gsBpcg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-collection": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-direction": "1.0.0",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "@radix-ui/react-use-controllable-state": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
+ "integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
+ "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
+ "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-callback-ref": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
+ "integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-callback-ref": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
+ "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
+ "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/rect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
+ "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
+ "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
"node_modules/@reduxjs/toolkit": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz",
@@ -2039,6 +2424,14 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "node_modules/@stitches/react": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@stitches/react/-/react-1.2.8.tgz",
+ "integrity": "sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==",
+ "peerDependencies": {
+ "react": ">= 16.3.0"
+ }
+ },
"node_modules/@testing-library/cypress": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-8.0.3.tgz",
@@ -2454,6 +2847,12 @@
"@types/unist": "*"
}
},
+ "node_modules/@types/history": {
+ "version": "4.7.11",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
+ "dev": true
+ },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -2756,6 +3155,27 @@
"redux": "^4.0.0"
}
},
+ "node_modules/@types/react-router": {
+ "version": "5.1.19",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz",
+ "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==",
+ "dev": true,
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
+ "dev": true,
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
"node_modules/@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -3612,6 +4032,26 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.1.tgz",
+ "integrity": "sha512-PN344VAf9j1EAi+jyVHOJ8XidQdPVssGco39eNcsGdM4wcsILtxrKLkbuiMfLWYROK1FjRQasMWCBttrhjnr6A==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.9.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/aria-query": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz",
@@ -6169,6 +6609,11 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
+ },
"node_modules/diff-sequences": {
"version": "28.1.1",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz",
@@ -8638,6 +9083,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -9140,11 +9593,23 @@
"he": "bin/he"
}
},
+ "node_modules/history": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
+ "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
+ "dependencies": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^3.0.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^1.0.1"
+ }
+ },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "devOptional": true,
"dependencies": {
"react-is": "^16.7.0"
}
@@ -9152,8 +9617,7 @@
"node_modules/hoist-non-react-statics/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "devOptional": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/hosted-git-info": {
"version": "2.8.9",
@@ -10024,6 +10488,14 @@
"node": ">= 0.10"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/ip": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
@@ -13278,7 +13750,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "devOptional": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -13840,6 +14311,19 @@
"node": ">=4"
}
},
+ "node_modules/mini-create-react-context": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
+ "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.1",
+ "tiny-warning": "^1.0.3"
+ },
+ "peerDependencies": {
+ "prop-types": "^15.0.0",
+ "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -14269,7 +14753,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "devOptional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -15266,7 +15749,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -15276,8 +15758,7 @@
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/property-information": {
"version": "6.1.1",
@@ -15666,7 +16147,6 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
- "devOptional": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -15679,7 +16159,6 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -15740,6 +16219,128 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"devOptional": true
},
+ "node_modules/react-remove-scroll": {
+ "version": "2.5.4",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
+ "integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.3",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.0",
+ "use-sidecar": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.3.tgz",
+ "integrity": "sha512-i9GMNWwpz8XpUpQ6QlevUtFjHGqnPG4Hxs+wlIJntu/xcsZVEpJcIV71K3ZkqNy2q3GfgvkD7y6t/Sv8ofYSbw==",
+ "dependencies": {
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
+ "integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.13",
+ "history": "^4.9.0",
+ "hoist-non-react-statics": "^3.1.0",
+ "loose-envify": "^1.3.1",
+ "mini-create-react-context": "^0.4.0",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.2",
+ "react-is": "^16.6.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=15"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.13",
+ "history": "^4.9.0",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.6.2",
+ "react-router": "5.3.3",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=15"
+ }
+ },
+ "node_modules/react-router/node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+ },
+ "node_modules/react-router/node_modules/path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "dependencies": {
+ "isarray": "0.0.1"
+ }
+ },
+ "node_modules/react-router/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
+ "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "invariant": "^2.2.4",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
@@ -16148,6 +16749,11 @@
"node": ">=8"
}
},
+ "node_modules/resolve-pathname": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
+ "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
+ },
"node_modules/resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -16373,7 +16979,6 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -17975,11 +18580,15 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
+ "node_modules/tiny-invariant": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
+ "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
+ },
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
- "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
- "dev": true
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"node_modules/tmp": {
"version": "0.2.1",
@@ -18335,8 +18944,7 @@
"node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
- "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
- "dev": true
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -19101,6 +19709,26 @@
"node": ">=0.10.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz",
+ "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/use-inline-memo": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/use-inline-memo/-/use-inline-memo-0.2.0.tgz",
@@ -19130,6 +19758,40 @@
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==",
"dev": true
},
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
+ "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
@@ -19196,6 +19858,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "node_modules/value-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
+ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -20511,7 +21178,11 @@
"dependencies": {
"@curvenote/emoji": "1.0.1",
"@curvenote/prosemirror-utils": "^1.0.4",
+ "@radix-ui/colors": "^0.1.8",
+ "@radix-ui/react-dropdown-menu": "^1.0.0",
+ "@radix-ui/react-icons": "^1.1.1",
"@reduxjs/toolkit": "^1.8.3",
+ "@stitches/react": "^1.2.8",
"codemirror": "5.65.0",
"katex": "^0.15.2",
"lodash.debounce": "^4.0.8",
@@ -20525,6 +21196,7 @@
"prosemirror-inputrules": "^1.2.0",
"prosemirror-keymap": "^1.2.0",
"prosemirror-tables": "^1.1.1",
+ "react-router-dom": "^5",
"scroll-into-view-if-needed": "^2.2.29",
"ts-jest": "^28.0.5",
"uuid": "^8.3.2"
@@ -20552,6 +21224,7 @@
"@types/react": "^17.0.47",
"@types/react-dom": "^17.0.17",
"@types/react-redux": "^7.1.24",
+ "@types/react-router-dom": "^5.3.3",
"@types/uuid": "^8.3.4",
"classnames": "^2.3.1",
"copy-webpack-plugin": "^11.0.0",
@@ -22180,7 +22853,11 @@
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@material-ui/pickers": "^3.3.10",
+ "@radix-ui/colors": "^0.1.8",
+ "@radix-ui/react-dropdown-menu": "^1.0.0",
+ "@radix-ui/react-icons": "^1.1.1",
"@reduxjs/toolkit": "^1.8.3",
+ "@stitches/react": "^1.2.8",
"@testing-library/cypress": "^8.0.3",
"@testing-library/dom": "^8.14.0",
"@testing-library/jest-dom": "^5.16.4",
@@ -22196,6 +22873,7 @@
"@types/react": "^17.0.47",
"@types/react-dom": "^17.0.17",
"@types/react-redux": "^7.1.24",
+ "@types/react-router-dom": "*",
"@types/uuid": "^8.3.4",
"classnames": "^2.3.1",
"codemirror": "5.65.0",
@@ -22235,6 +22913,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^8.0.2",
+ "react-router-dom": "^5",
"redux": "^4.2.0",
"redux-thunk": "^2.4.1",
"sass": "^1.53.0",
@@ -22714,6 +23393,28 @@
}
}
},
+ "@floating-ui/core": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
+ "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
+ },
+ "@floating-ui/dom": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
+ "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
+ "requires": {
+ "@floating-ui/core": "^0.7.3"
+ }
+ },
+ "@floating-ui/react-dom": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
+ "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
+ "requires": {
+ "@floating-ui/dom": "^0.5.3",
+ "use-isomorphic-layout-effect": "^1.1.1"
+ }
+ },
"@humanwhocodes/config-array": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
@@ -23453,6 +24154,283 @@
"fastq": "^1.6.0"
}
},
+ "@radix-ui/colors": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-0.1.8.tgz",
+ "integrity": "sha512-jwRMXYwC0hUo0mv6wGpuw254Pd9p/R6Td5xsRpOmaWkUHlooNWqVcadgyzlRumMq3xfOTXwJReU0Jv+EIy4Jbw=="
+ },
+ "@radix-ui/primitive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
+ "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "@radix-ui/react-arrow": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
+ "integrity": "sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-primitive": "1.0.0"
+ }
+ },
+ "@radix-ui/react-collection": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.0.tgz",
+ "integrity": "sha512-8i1pf5dKjnq90Z8udnnXKzdCEV3/FYrfw0n/b6NvB6piXEn3fO1bOh7HBcpG8XrnIXzxlYu2oCcR38QpyLS/mg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-slot": "1.0.0"
+ }
+ },
+ "@radix-ui/react-compose-refs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
+ "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "@radix-ui/react-context": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
+ "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "@radix-ui/react-direction": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
+ "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "@radix-ui/react-dismissable-layer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
+ "integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "@radix-ui/react-use-escape-keydown": "1.0.0"
+ }
+ },
+ "@radix-ui/react-dropdown-menu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-1.0.0.tgz",
+ "integrity": "sha512-Ptben3TxPWrZLbInO7zjAK73kmjYuStsxfg6ujgt+EywJyREoibhZYnsSNqC+UiOtl4PdW/MOHhxVDtew5fouQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-menu": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-controllable-state": "1.0.0"
+ }
+ },
+ "@radix-ui/react-focus-guards": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
+ "integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "@radix-ui/react-focus-scope": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
+ "integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0"
+ }
+ },
+ "@radix-ui/react-icons": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.1.1.tgz",
+ "integrity": "sha512-xc3wQC59rsFylVbSusQCrrM+6695ppF730Q6yqzhRdqDcRNWIm2R6ngpzBoSOQMcwnq4p805F+Gr7xo4fmtN1A==",
+ "requires": {}
+ },
+ "@radix-ui/react-id": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
+ "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ }
+ },
+ "@radix-ui/react-menu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-1.0.0.tgz",
+ "integrity": "sha512-icW4C64T6nHh3Z4Q1fxO1RlSShouFF4UpUmPV8FLaJZfphDljannKErDuALDx4ClRLihAPZ9i+PrLNPoWS2DMA==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-collection": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-direction": "1.0.0",
+ "@radix-ui/react-dismissable-layer": "1.0.0",
+ "@radix-ui/react-focus-guards": "1.0.0",
+ "@radix-ui/react-focus-scope": "1.0.0",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-popper": "1.0.0",
+ "@radix-ui/react-portal": "1.0.0",
+ "@radix-ui/react-presence": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-roving-focus": "1.0.0",
+ "@radix-ui/react-slot": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.5.4"
+ }
+ },
+ "@radix-ui/react-popper": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.0.tgz",
+ "integrity": "sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@floating-ui/react-dom": "0.7.2",
+ "@radix-ui/react-arrow": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0",
+ "@radix-ui/react-use-rect": "1.0.0",
+ "@radix-ui/react-use-size": "1.0.0",
+ "@radix-ui/rect": "1.0.0"
+ }
+ },
+ "@radix-ui/react-portal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
+ "integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-primitive": "1.0.0"
+ }
+ },
+ "@radix-ui/react-presence": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
+ "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ }
+ },
+ "@radix-ui/react-primitive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
+ "integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-slot": "1.0.0"
+ }
+ },
+ "@radix-ui/react-roving-focus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.0.tgz",
+ "integrity": "sha512-lHvO4MhvoWpeNbiJAoyDsEtbKqP2jkkdwsMVJ3kfqbkC71J/aXE6Th6gkZA1xHEqSku+t+UgoDjvE7Z3gsBpcg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-collection": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-direction": "1.0.0",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.0",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "@radix-ui/react-use-controllable-state": "1.0.0"
+ }
+ },
+ "@radix-ui/react-slot": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
+ "integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0"
+ }
+ },
+ "@radix-ui/react-use-callback-ref": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
+ "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "@radix-ui/react-use-controllable-state": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
+ "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-callback-ref": "1.0.0"
+ }
+ },
+ "@radix-ui/react-use-escape-keydown": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
+ "integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-callback-ref": "1.0.0"
+ }
+ },
+ "@radix-ui/react-use-layout-effect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
+ "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "@radix-ui/react-use-rect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
+ "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/rect": "1.0.0"
+ }
+ },
+ "@radix-ui/react-use-size": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
+ "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ }
+ },
+ "@radix-ui/rect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
+ "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
"@reduxjs/toolkit": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz",
@@ -23491,6 +24469,12 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "@stitches/react": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@stitches/react/-/react-1.2.8.tgz",
+ "integrity": "sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==",
+ "requires": {}
+ },
"@testing-library/cypress": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-8.0.3.tgz",
@@ -23839,6 +24823,12 @@
"@types/unist": "*"
}
},
+ "@types/history": {
+ "version": "4.7.11",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
+ "dev": true
+ },
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -24142,6 +25132,27 @@
"redux": "^4.0.0"
}
},
+ "@types/react-router": {
+ "version": "5.1.19",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz",
+ "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==",
+ "dev": true,
+ "requires": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*"
+ }
+ },
+ "@types/react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
+ "dev": true,
+ "requires": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
"@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -24797,6 +25808,14 @@
"sprintf-js": "~1.0.2"
}
},
+ "aria-hidden": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.1.tgz",
+ "integrity": "sha512-PN344VAf9j1EAi+jyVHOJ8XidQdPVssGco39eNcsGdM4wcsILtxrKLkbuiMfLWYROK1FjRQasMWCBttrhjnr6A==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
"aria-query": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz",
@@ -26741,6 +27760,11 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true
},
+ "detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
+ },
"diff-sequences": {
"version": "28.1.1",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz",
@@ -28532,6 +29556,11 @@
"has-symbols": "^1.0.3"
}
},
+ "get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
+ },
"get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -28900,11 +29929,23 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
+ "history": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
+ "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^3.0.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^1.0.1"
+ }
+ },
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "devOptional": true,
"requires": {
"react-is": "^16.7.0"
},
@@ -28912,8 +29953,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "devOptional": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
@@ -29575,6 +30615,14 @@
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"dev": true
},
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"ip": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
@@ -31956,7 +33004,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "devOptional": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
@@ -32345,6 +33392,15 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
},
+ "mini-create-react-context": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
+ "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "tiny-warning": "^1.0.3"
+ }
+ },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -32703,8 +33759,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "devOptional": true
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"object-copy": {
"version": "0.1.0",
@@ -33430,7 +34485,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -33440,8 +34494,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
@@ -34150,7 +35203,6 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
- "devOptional": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -34160,7 +35212,6 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
- "dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -34195,6 +35246,88 @@
}
}
},
+ "react-remove-scroll": {
+ "version": "2.5.4",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
+ "integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
+ "requires": {
+ "react-remove-scroll-bar": "^2.3.3",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.0",
+ "use-sidecar": "^1.1.2"
+ }
+ },
+ "react-remove-scroll-bar": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.3.tgz",
+ "integrity": "sha512-i9GMNWwpz8XpUpQ6QlevUtFjHGqnPG4Hxs+wlIJntu/xcsZVEpJcIV71K3ZkqNy2q3GfgvkD7y6t/Sv8ofYSbw==",
+ "requires": {
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.0.0"
+ }
+ },
+ "react-router": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
+ "integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
+ "requires": {
+ "@babel/runtime": "^7.12.13",
+ "history": "^4.9.0",
+ "hoist-non-react-statics": "^3.1.0",
+ "loose-envify": "^1.3.1",
+ "mini-create-react-context": "^0.4.0",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.2",
+ "react-is": "^16.6.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+ },
+ "path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
+ "react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==",
+ "requires": {
+ "@babel/runtime": "^7.12.13",
+ "history": "^4.9.0",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.6.2",
+ "react-router": "5.3.3",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ }
+ },
+ "react-style-singleton": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
+ "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
+ "requires": {
+ "get-nonce": "^1.0.0",
+ "invariant": "^2.2.4",
+ "tslib": "^2.0.0"
+ }
+ },
"react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
@@ -34505,6 +35638,11 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
},
+ "resolve-pathname": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
+ "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
+ },
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -34647,7 +35785,6 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
- "dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -35938,11 +37075,15 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
+ "tiny-invariant": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
+ "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
+ },
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
- "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
- "dev": true
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tmp": {
"version": "0.2.1",
@@ -36188,8 +37329,7 @@
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
- "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
- "dev": true
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"tsutils": {
"version": "3.21.0",
@@ -36711,6 +37851,14 @@
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
"dev": true
},
+ "use-callback-ref": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz",
+ "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
"use-inline-memo": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/use-inline-memo/-/use-inline-memo-0.2.0.tgz",
@@ -36739,6 +37887,21 @@
}
}
},
+ "use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "requires": {}
+ },
+ "use-sidecar": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
+ "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
+ "requires": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ }
+ },
"use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
@@ -36794,6 +37957,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "value-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
+ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
diff --git a/packages/editor/cypress/component/figure.cy.tsx b/packages/editor/cypress/component/figure.cy.tsx
new file mode 100644
index 00000000..c7746153
--- /dev/null
+++ b/packages/editor/cypress/component/figure.cy.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { render, waitFor } from './utils';
+import userEvent from '@testing-library/user-event';
+import { createStore, DemoEditor } from '../../demo/NewEditorDemo';
+
+// TODO: abstract these they are copy pasted from demo since the setup logic is shared
+const stateKey = 'myEditor';
+const viewId1 = 'view1';
+
+const TABLE_CONTENT = `
Header 1 Header 2 Header 3 Data over here Multiple columns! 4 5 6
`;
+const FIGURE_CONTENT = `
+function sayHello() {
+ console.log("Hello editing!");
+}
+`;
+const IMAGE_CONTENT =
+ ' ';
+const IFRAME_CONTENT =
+ '';
+function generateFigureSnippet(content: string) {
+ const SNIPPET_WITH_TABLE = `
+
+
+ ${content}
+ this is caption!
+
+
+ `;
+ return SNIPPET_WITH_TABLE;
+}
+const TABLE_FIGURE_SNIPPET = generateFigureSnippet(TABLE_CONTENT);
+const CODEBLOCK_FIGURE_SNIPPET = generateFigureSnippet(TABLE_CONTENT);
+const IMAGE_FIGURE_SNIPPET = generateFigureSnippet(IMAGE_CONTENT);
+const IFRAME_FIGURE_SNIPPET = generateFigureSnippet(IFRAME_CONTENT);
+
+it('should render curvenote link with the correct attributes', () => {
+ const { container } = render( );
+ expect(container.querySelector('table')).to.be.instanceOf(HTMLTableElement);
+ expect(container.querySelector('figcaption')?.innerHTML).to.be.eq('this is caption!');
+});
+
+[
+ TABLE_FIGURE_SNIPPET,
+ CODEBLOCK_FIGURE_SNIPPET,
+ IMAGE_FIGURE_SNIPPET,
+ IFRAME_FIGURE_SNIPPET,
+].forEach((snippet) => {
+ it('should create one new paragraph below the figure if hit enter within figure caption', async () => {
+ const store = createStore();
+ const { container } = render(
+ ,
+ );
+ await waitFor(() => {
+ expect(container.querySelector('figcaption')).to.be.ok;
+ })
+ const figcaption = container.querySelector('figcaption') as HTMLElement;
+ userEvent.click(figcaption);
+ await userEvent.keyboard('hello{Enter}{Enter}', { delay: 10 });
+ await waitFor(() => {
+ expect(
+ container.querySelectorAll('#first > div')[1]?.querySelectorAll(':scope > p').length, // there should be one and only one paragraph gets created;
+ ).to.eq(1);
+ });
+ });
+});
diff --git a/packages/editor/demo/NewEditorDemo.tsx b/packages/editor/demo/NewEditorDemo.tsx
new file mode 100644
index 00000000..f912ed00
--- /dev/null
+++ b/packages/editor/demo/NewEditorDemo.tsx
@@ -0,0 +1,148 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { Provider } from 'react-redux';
+import { createTheme } from '@material-ui/core';
+import { ReferenceKind } from '@curvenote/schema';
+import { Fragment } from 'prosemirror-model';
+import type { Store, LinkResult } from '../src';
+import {
+ middleware,
+ EditorMenu,
+ Suggestions,
+ actions,
+ createEditor,
+ Attributes,
+ InlineActions,
+} from '../src';
+import SuggestionSwitch from '../src/components/Suggestion/Switch';
+import InlineActionSwitch from '../src/components/InlineActions/Switch';
+import Editor from '../src/components/NextEditor';
+import rootReducer from './reducers';
+import 'codemirror/lib/codemirror.css';
+import '../styles/index.scss';
+import 'sidenotes/dist/sidenotes.css';
+import type { Options } from '../src/connect';
+import { configureStore } from '@reduxjs/toolkit';
+
+declare global {
+ interface Window {
+ [index: string]: any;
+ }
+}
+
+const DEFAULT_STATE_KEY = 'myEditor';
+const viewId1 = 'view1';
+const docId = 'docId';
+const someLinks: LinkResult[] = [
+ {
+ kind: ReferenceKind.cite,
+ uid: 'simpeg2015',
+ label: 'simpeg',
+ content: 'Cockett et al., 2015',
+ title:
+ 'SimPEG: An open source framework for simulation and gradient based parameter estimation in geophysical applications.',
+ },
+ {
+ kind: ReferenceKind.link,
+ uid: 'https://curvenote.com',
+ label: null,
+ content: 'Curvenote',
+ title: 'Move ideas forward',
+ },
+];
+
+export function createStore(): Store {
+ return configureStore({
+ reducer: rootReducer,
+ middleware: (getDefaultMiddleware: any) => {
+ return [
+ ...middleware,
+ ...getDefaultMiddleware({
+ serializableCheck: false,
+ immutableCheck: false,
+ }),
+ ];
+ },
+ });
+}
+
+const theme = createTheme({});
+function createOptions(store: Store) {
+ const newComment = () => {
+ store.dispatch(actions.addCommentToSelectedView('sidenote1'));
+ };
+ const removeComment = () => {
+ store.dispatch(actions.removeComment(viewId1, 'sidenote1'));
+ };
+ return {
+ transformKeyToId: (key) => key,
+ uploadImage: async (file) => {
+ // eslint-disable-next-line no-console
+ console.log(file);
+ return new Promise((resolve) =>
+ setTimeout(() => resolve('https://curvenote.dev/images/logo.png'), 2000),
+ );
+ },
+ getDocId() {
+ return docId;
+ },
+ addComment() {
+ newComment();
+ return true;
+ },
+ onDoubleClick(stateId, viewId) {
+ // eslint-disable-next-line no-console
+ console.log('Double click', stateId, viewId);
+ return false;
+ },
+ theme,
+ citationPrompt: async () => [
+ {
+ key: 'simpeg2015',
+ kind: ReferenceKind.cite,
+ text: 'Cockett et al, 2015',
+ label: 'simpeg',
+ title: '',
+ },
+ ],
+ createLinkSearch: async () => ({ search: () => someLinks }),
+ getCaptionFragment: (schema) => Fragment.fromArray([schema.text('Hello caption world!')]),
+ nodeViews: {},
+ } as Options;
+}
+
+export function DemoEditor({
+ content,
+ store = createStore(),
+ stateKey = DEFAULT_STATE_KEY,
+ viewId = viewId1,
+}: {
+ content: string;
+ store?: Store;
+ viewId?: string;
+ stateKey?: string;
+}) {
+ const editor = useMemo(() => createEditor(store, createOptions(store)), []);
+ useEffect(() => {
+ window.store = store;
+ return () => {
+ window.store = undefined;
+ };
+ }, [store]);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/editor/demo/index.html b/packages/editor/demo/index.html
index 13e976c5..3e638f3e 100644
--- a/packages/editor/demo/index.html
+++ b/packages/editor/demo/index.html
@@ -65,7 +65,7 @@
crossorigin="anonymous"
/>
-
+
You need to enable JavaScript to run this app.
@curvenote/editor
diff --git a/packages/editor/demo/index.tsx b/packages/editor/demo/index.tsx
index ca03dc55..5e54287e 100644
--- a/packages/editor/demo/index.tsx
+++ b/packages/editor/demo/index.tsx
@@ -1,6 +1,57 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { DemoEditor } from './init';
-import snippet from './snippet';
+import { DemoEditor as NextEditor } from './NewEditorDemo';
+import snippet, { snippetNext } from './snippet';
+import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
+import { styled } from '@stitches/react';
-ReactDOM.render(
, document.getElementById('root'));
+const Container = styled('div', {
+ margin: 'auto',
+ width: 800,
+});
+const Ul = styled('ul', {
+ listStyle: 'none',
+ fontSize: 24,
+ marginTop: 16,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ li: {
+ padding: 16,
+ },
+});
+
+function App() {
+ return (
+ <>
+
+
+
+
+ Legacy
+
+
+ Article
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root'),
+);
diff --git a/packages/editor/demo/init.tsx b/packages/editor/demo/init.tsx
index 3eb41bae..f338f9a5 100644
--- a/packages/editor/demo/init.tsx
+++ b/packages/editor/demo/init.tsx
@@ -5,23 +5,14 @@ import { Button, createTheme } from '@material-ui/core';
import { toHTML, toMarkdown, toTex, ReferenceKind, process, toText } from '@curvenote/schema';
import { Sidenote, AnchorBase } from 'sidenotes';
import { Fragment } from 'prosemirror-model';
-import {
- actions,
- Editor,
- EditorMenu,
- Store,
- setup,
- Suggestions,
- Attributes,
- InlineActions,
- LinkResult,
-} from '../src';
+import type { Store, LinkResult } from '../src';
+import { actions, Editor, EditorMenu, setup, Suggestions, Attributes, InlineActions } from '../src';
import rootReducer from './reducers';
import middleware from './middleware';
import 'codemirror/lib/codemirror.css';
import '../styles/index.scss';
import 'sidenotes/dist/sidenotes.css';
-import { Options } from '../src/connect';
+import type { Options } from '../src/connect';
import SuggestionSwitch from '../src/components/Suggestion/Switch';
import InlineActionSwitch from '../src/components/InlineActions/Switch';
@@ -142,15 +133,15 @@ export function DemoEditor({ content, store = createStore() }: { content: string
}
// Update the counter
const counts = process.countState(editor.state);
- const words = process.countWords(editor.state);
+ // const words = process.countWords(editor.state);
const updates = {
'count-sec': `${counts.sec.all.length} (${counts.sec.total})`,
'count-fig': `${counts.fig.all.length} (${counts.fig.total})`,
'count-eq': `${counts.eq.all.length} (${counts.eq.total})`,
'count-code': `${counts.code.all.length} (${counts.code.total})`,
'count-table': `${counts.table.all.length} (${counts.table.total})`,
- 'count-words': `${words.words}`,
- 'count-char': `${words.characters_including_spaces} (${words.characters_excluding_spaces})`,
+ // 'count-words': `${words.words}`,
+ // 'count-char': `${words.characters_including_spaces} (${words.characters_excluding_spaces})`,
};
Object.entries(updates).forEach(([key, count]) => {
const el = document.getElementById(key);
diff --git a/packages/editor/demo/reducers.ts b/packages/editor/demo/reducers.ts
index 06b703c1..67e8736d 100644
--- a/packages/editor/demo/reducers.ts
+++ b/packages/editor/demo/reducers.ts
@@ -1,7 +1,8 @@
import runtime from '@curvenote/runtime';
import * as sidenotes from 'sidenotes';
import { combineReducers } from 'redux';
-import { State, EditorActions, Reducer, reducer } from '../src';
+import type { State, EditorActions, Reducer } from '../src';
+import { reducer } from '../src';
const combinedReducers: Reducer = combineReducers({
editor: reducer,
diff --git a/packages/editor/demo/snippet.html b/packages/editor/demo/snippet.html
index 932b9787..7f568c35 100644
--- a/packages/editor/demo/snippet.html
+++ b/packages/editor/demo/snippet.html
@@ -1,4 +1,5 @@
-
Welcome to @curvenote/editor!
+
Welcome to @curvenote/editor!
+
Feel free to ➡️ edit this document ⬅️, or for collaborative editing, check out
curvenote.com , which is a scientific writing platform that
@@ -32,6 +33,8 @@
Welcome to @curvenote/editor!
for code or ``` for a code block!
+
+
You can also configure the editor to show citations, like this one from Geophysics
. Try
@@ -81,6 +84,8 @@
You can insert code:
You can insert footnotes:
This is a footnote. You can access it using the /footnote command
+
+
Speaking of commands! Try starting with a forward slash / after this callout
@@ -128,3 +133,6 @@
Interactivity
align="center"
width="70%"
>
+
+
+
diff --git a/packages/editor/demo/snippet.ts b/packages/editor/demo/snippet.ts
index 1a9e1e41..697ca568 100644
--- a/packages/editor/demo/snippet.ts
+++ b/packages/editor/demo/snippet.ts
@@ -1,5 +1,7 @@
import html from './snippet.html';
+import nextSnippet from './snippetNext.html';
const snippet = html;
+export const snippetNext = nextSnippet;
export default snippet;
diff --git a/packages/editor/demo/snippetNext.html b/packages/editor/demo/snippetNext.html
new file mode 100644
index 00000000..bbd410f5
--- /dev/null
+++ b/packages/editor/demo/snippetNext.html
@@ -0,0 +1,141 @@
+
+
Welcome to @curvenote/editor!
+
+
+
+ Feel free to ➡️ edit this document ⬅️, or for collaborative editing, check out
+ curvenote.com , which is a scientific writing platform that
+ connects to Jupyter. We would love if you came and supported us! ❤️
+
+
+
+ The Curvenote logo!
+
+
@curvenote/editor is a markdown accelerated editor.
+
+
+
+ # Starting a line with a hash creates a header. Multiple ##’s
+ creates a smaller header
+
+
+
+
+ * Will create a bullet list. For numbered lists use 1. or you can
+ start at a higher number!
+
+
+
+ / Will get you a list of the commands you can do
+
+
+ : for emotion 🎉 🥳 🚀
+
+
+ for code or ``` for a code block!
+
+
+
+
+
+ You can also configure the editor to show citations, like this one from Geophysics
+ . Try
+ clicking on the citation! Or choose one from the list with a [[ command
+
+
You can insert tables:
+
+
+ This is my table!
+
+
+ Header 1
+ Header 2
+ Header 3
+
+
+ Data over here
+ Multiple columns!
+
+
+ 4
+ 5
+ 6
+
+
+
+
You can insert code:
+
+
function sayHello() {
+ console.log("Hello editing!");
+}
+
+
You can insert footnotes:
+
This is a footnote. You can access it using the /footnote command
+
+
+
+
+
This is a link block
+
link block description
+
+
+ You can add all sorts of things, like call out panels, asides,
+ \LaTeX , equations, or embed videos!
+
+
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
+
+ This will also translate to various other markup languages, using
+ @curvenote/schema , you can see MyST,
+ \LaTeX and HTML below.
+
+
Interactivity
+
+
+
+
+
+ Visitors:
+
+
+
+
+
+
+ Cost:
+
+
+
+
+
+ If there are visitors and the admission
+ cost is , the park will make
+ .
+
+
+
+
+
+
diff --git a/packages/editor/package.json b/packages/editor/package.json
index 515440e5..99ab4288 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -40,7 +40,11 @@
"dependencies": {
"@curvenote/emoji": "1.0.1",
"@curvenote/prosemirror-utils": "^1.0.4",
+ "@radix-ui/colors": "^0.1.8",
+ "@radix-ui/react-dropdown-menu": "^1.0.0",
+ "@radix-ui/react-icons": "^1.1.1",
"@reduxjs/toolkit": "^1.8.3",
+ "@stitches/react": "^1.2.8",
"codemirror": "5.65.0",
"katex": "^0.15.2",
"lodash.debounce": "^4.0.8",
@@ -54,6 +58,7 @@
"prosemirror-inputrules": "^1.2.0",
"prosemirror-keymap": "^1.2.0",
"prosemirror-tables": "^1.1.1",
+ "react-router-dom": "^5",
"scroll-into-view-if-needed": "^2.2.29",
"ts-jest": "^28.0.5",
"uuid": "^8.3.2"
@@ -106,6 +111,7 @@
"@types/react": "^17.0.47",
"@types/react-dom": "^17.0.17",
"@types/react-redux": "^7.1.24",
+ "@types/react-router-dom": "^5.3.3",
"@types/uuid": "^8.3.4",
"classnames": "^2.3.1",
"copy-webpack-plugin": "^11.0.0",
diff --git a/packages/editor/src/components/Attributes/index.tsx b/packages/editor/src/components/Attributes/index.tsx
index b90c4d18..12ea466f 100644
--- a/packages/editor/src/components/Attributes/index.tsx
+++ b/packages/editor/src/components/Attributes/index.tsx
@@ -1,17 +1,10 @@
import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import isEqual from 'lodash.isequal';
-import {
- TextField,
- makeStyles,
- Theme,
- createStyles,
- Popover,
- Paper,
- Typography,
-} from '@material-ui/core';
-import { schemas } from '@curvenote/schema';
-import { State, Dispatch } from '../../store/types';
+import type { Theme } from '@material-ui/core';
+import { TextField, makeStyles, createStyles, Popover, Paper, Typography } from '@material-ui/core';
+import { schemas, NODE_GROUPS } from '@curvenote/schema';
+import type { State, Dispatch } from '../../store/types';
import { closeAttributeEditor, updateNodeAttrs } from '../../store/actions';
import {
getEditorUI,
@@ -42,7 +35,9 @@ const useStyles = makeStyles((theme: Theme) =>
}),
);
-export const NODES_WITH_ATTRS = new Set(Object.keys(schemas.reactiveNodes));
+export const NODES_WITH_ATTRS = new Set(
+ Object.keys(schemas.createReactiveNodeSpec(NODE_GROUPS.legacy)),
+);
function Attributes() {
const classes = useStyles();
diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx
index ba1a5810..54236865 100644
--- a/packages/editor/src/components/Editor.tsx
+++ b/packages/editor/src/components/Editor.tsx
@@ -1,20 +1,22 @@
import React, { useEffect, useRef } from 'react';
-import { EditorView } from 'prosemirror-view';
+import type { EditorView } from 'prosemirror-view';
import { useDispatch, useSelector } from 'react-redux';
-import { EditorState, Transaction } from 'prosemirror-state';
+import type { EditorState, Transaction } from 'prosemirror-state';
import { opts } from '../connect';
import { createEditorView } from '../prosemirror';
-import { Dispatch, State, actions, selectors } from '../store';
+import type { Dispatch, State } from '../store';
+import { actions, selectors } from '../store';
type Props = {
stateKey: any;
viewId: string;
className?: string;
autoUnsubscribe?: boolean;
+ store?: any;
};
const Editor = (props: Props) => {
- const { stateKey, viewId, className, autoUnsubscribe } = props;
+ const { stateKey, viewId, className, autoUnsubscribe, store } = props;
const dispatch = useDispatch();
@@ -38,6 +40,7 @@ const Editor = (props: Props) => {
// Immidiately update the view.
// This is important for properly handling selections.
// Cannot use react event loop here.
+
editorView.current?.updateState(next);
});
editorView.current.dom.id = viewId;
diff --git a/packages/editor/src/components/InlineActions/CalloutActions.tsx b/packages/editor/src/components/InlineActions/CalloutActions.tsx
index ef02c689..a9e64306 100644
--- a/packages/editor/src/components/InlineActions/CalloutActions.tsx
+++ b/packages/editor/src/components/InlineActions/CalloutActions.tsx
@@ -1,14 +1,14 @@
import React from 'react';
import { makeStyles, createStyles, Grid } from '@material-ui/core';
import { findParentNode } from '@curvenote/prosemirror-utils';
-import { Node } from 'prosemirror-model';
+import type { Node } from 'prosemirror-model';
import { nodeNames } from '@curvenote/schema';
import { useDispatch, useSelector } from 'react-redux';
import MenuIcon from '../Menu/Icon';
import { deleteNode, liftContentOutOfNode, updateNodeAttrs } from '../../store/actions';
import { getEditorState } from '../../store/state/selectors';
-import { Dispatch, State } from '../../store';
-import { ActionProps } from './utils';
+import type { Dispatch, State } from '../../store';
+import type { ActionProps } from './utils';
import { getNodeFromSelection } from '../../store/ui/utils';
const useStyles = makeStyles(() =>
diff --git a/packages/editor/src/components/InlineActions/CodeActions.tsx b/packages/editor/src/components/InlineActions/CodeActions.tsx
index db900b44..d90a2b56 100644
--- a/packages/editor/src/components/InlineActions/CodeActions.tsx
+++ b/packages/editor/src/components/InlineActions/CodeActions.tsx
@@ -8,12 +8,18 @@ import {
createStyles,
Grid,
} from '@material-ui/core';
-import { findParentNode, replaceParentNodeOfType } from '@curvenote/prosemirror-utils';
-import { Node, NodeType } from 'prosemirror-model';
-import { CaptionKind, nodeNames, Nodes } from '@curvenote/schema';
+import {
+ findParentNode,
+ findParentNodeOfTypeClosestToPos,
+ replaceParentNodeOfType,
+} from '@curvenote/prosemirror-utils';
+import type { Node, NodeType } from 'prosemirror-model';
+import type { Nodes } from '@curvenote/schema';
+import { CaptionKind, nodeNames } from '@curvenote/schema';
import { useDispatch, useSelector } from 'react-redux';
import { NodeSelection, TextSelection } from 'prosemirror-state';
-import { LanguageNames, SUPPORTED_LANGUAGES } from '../../views/types';
+import type { LanguageNames } from '../../views/types';
+import { SUPPORTED_LANGUAGES } from '../../views/types';
import MenuIcon from '../Menu/Icon';
import {
applyProsemirrorTransaction,
@@ -23,8 +29,9 @@ import {
} from '../../store/actions';
import { updateNodeAttrs } from '../../store/actions/editor';
import { getEditorState } from '../../store/state/selectors';
-import { Dispatch, State } from '../../store';
-import { ActionProps, getFigure } from './utils';
+import type { Dispatch, State } from '../../store';
+import type { ActionProps } from './utils';
+import { getFigure } from './utils';
import { getNodeFromSelection } from '../../store/ui/utils';
const useStyles = makeStyles(() =>
@@ -118,7 +125,23 @@ function CodeActions(props: ActionProps) {
if (!editorState || !node || pos == null) return null;
- const onDelete = () => dispatch(deleteNode(stateId, viewId, { node, pos }));
+ const onDelete = () => {
+ const foundParentFigureResult = findParentNodeOfTypeClosestToPos(
+ selection.$from,
+ editorState.schema.nodes[nodeNames.figure],
+ );
+ // delete figure if exists
+ if (foundParentFigureResult) {
+ dispatch(
+ deleteNode(stateId, viewId, {
+ node: foundParentFigureResult.node,
+ pos: foundParentFigureResult?.pos,
+ }),
+ );
+ } else {
+ dispatch(deleteNode(stateId, viewId, { node, pos }));
+ }
+ };
const onCaption = () => {
if (!figure) {
diff --git a/packages/editor/src/components/InlineActions/HeadingActions.tsx b/packages/editor/src/components/InlineActions/HeadingActions.tsx
index 3b086998..79b7f359 100644
--- a/packages/editor/src/components/InlineActions/HeadingActions.tsx
+++ b/packages/editor/src/components/InlineActions/HeadingActions.tsx
@@ -1,14 +1,14 @@
import React, { useCallback } from 'react';
import { makeStyles, createStyles, Grid, Menu } from '@material-ui/core';
-import { Node } from 'prosemirror-model';
+import type { Node } from 'prosemirror-model';
import { nodeNames } from '@curvenote/schema';
import { findParentNode } from '@curvenote/prosemirror-utils';
import { useDispatch, useSelector } from 'react-redux';
import MenuIcon from '../Menu/Icon';
import { updateNodeAttrs, wrapInHeading } from '../../store/actions';
import { getEditorState } from '../../store/selectors';
-import { Dispatch, State } from '../../store';
-import { ActionProps } from './utils';
+import type { Dispatch, State } from '../../store';
+import type { ActionProps } from './utils';
import MenuAction from '../Menu/Action';
import Keyboard from '../Keyboard';
import { getNodeFromSelection } from '../../store/ui/utils';
diff --git a/packages/editor/src/components/InlineActions/Switch.tsx b/packages/editor/src/components/InlineActions/Switch.tsx
index 817f9d18..0120fffa 100644
--- a/packages/editor/src/components/InlineActions/Switch.tsx
+++ b/packages/editor/src/components/InlineActions/Switch.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { useSelector } from 'react-redux';
import isEqual from 'lodash.isequal';
-import { State } from '../../store/types';
+import type { State } from '../../store/types';
import { SelectionKinds as Kinds } from '../../store/ui/types';
import { getEditorUIStateAndViewIds, getInlineActionKind } from '../../store/ui/selectors';
import LinkActions from './LinkActions';
@@ -28,7 +28,7 @@ export function useInlineActionProps() {
};
}
-function InlineActionSwitch() {
+function InlineActionSwitch({ hideCallout }: { hideCallout?: boolean }) {
const { stateId, viewId, kind } = useInlineActionProps();
return (
<>
@@ -37,7 +37,7 @@ function InlineActionSwitch() {
{kind === Kinds.figure && }
{kind === Kinds.image && }
{kind === Kinds.iframe && }
- {kind === Kinds.callout && }
+ {!hideCallout && kind === Kinds.callout && }
{kind === Kinds.heading && }
{/* {kind === Kinds.math && } */}
{kind === Kinds.equation && }
diff --git a/packages/editor/src/components/NextEditor.tsx b/packages/editor/src/components/NextEditor.tsx
new file mode 100644
index 00000000..dbd4604a
--- /dev/null
+++ b/packages/editor/src/components/NextEditor.tsx
@@ -0,0 +1,29 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Editor } from '../editor';
+
+type Props = {
+ editor: Editor;
+ stateKey: string;
+ // for redux, keep for now
+ viewId: string;
+ initialContent: string;
+};
+
+const Editor = (props: Props) => {
+ const { editor, initialContent, stateKey, viewId } = props;
+
+ const [editorDom, setEditorDom] = useState(null);
+ const ref = useCallback((el) => setEditorDom(el), []);
+
+ useEffect(() => {
+ if (!editorDom || !editor) return;
+ editor.init(editorDom, { content: initialContent, stateKey, viewId });
+ return () => {
+ editor.destroy();
+ };
+ }, [editorDom, editor]);
+
+ return
;
+};
+
+export default Editor;
diff --git a/packages/editor/src/components/index.ts b/packages/editor/src/components/index.ts
index 9b11e4b0..32278448 100644
--- a/packages/editor/src/components/index.ts
+++ b/packages/editor/src/components/index.ts
@@ -1,4 +1,5 @@
export { default as Editor } from './Editor';
+export { default as NextEditor } from './NextEditor';
export { default as EditorMenu } from './Menu';
export { default as SelectWidth } from './InlineActions/SelectWidth';
diff --git a/packages/editor/src/connect.ts b/packages/editor/src/connect.ts
index 3a2eb6e3..3b8446f3 100644
--- a/packages/editor/src/connect.ts
+++ b/packages/editor/src/connect.ts
@@ -1,7 +1,7 @@
/* eslint-disable no-underscore-dangle */
import type { Theme } from '@material-ui/core';
import * as sidenotes from 'sidenotes';
-import type { EditorState, Transaction } from 'prosemirror-state';
+import type { EditorState, Plugin, Transaction } from 'prosemirror-state';
import type { Node, Schema, Slice } from 'prosemirror-model';
import { Fragment } from 'prosemirror-model';
import type { EditorView, DirectEditorProps } from 'prosemirror-view';
@@ -19,6 +19,7 @@ export type UploadImageState = string;
export type Options = {
transformKeyToId: (key: any) => string | null;
+ plugins?: Plugin[];
handlePaste?: (view: EditorView, event: ClipboardEvent, slice: Slice) => boolean;
uploadImage: (file: File, node: Node | null) => Promise;
modifyTransaction?: (
@@ -55,7 +56,9 @@ type Ref = {
export const ref: Ref = {
store() {
- if (ref._store === undefined) throw new Error('Must init store.');
+ if (ref._store === undefined) {
+ throw new Error('Must init store.');
+ }
return ref._store;
},
opts() {
@@ -129,4 +132,7 @@ export const opts: Required = {
get nodeViews() {
return ref.opts().nodeViews ?? {};
},
+ get plugins() {
+ return ref.opts().plugins ?? [];
+ },
};
diff --git a/packages/editor/src/editor.ts b/packages/editor/src/editor.ts
new file mode 100644
index 00000000..0af10f48
--- /dev/null
+++ b/packages/editor/src/editor.ts
@@ -0,0 +1,145 @@
+import { process } from '@curvenote/schema';
+import { combineReducers, configureStore } from '@reduxjs/toolkit';
+import type { Options } from './connect';
+import { setup } from './connect';
+import { createEditorState, createEditorView } from './prosemirror';
+import type { EditorState, Transaction } from 'prosemirror-state';
+import type { EditorView } from 'prosemirror-view';
+import {
+ registerEditorState,
+ updateSelectView,
+ subscribeView,
+ updateEditorState,
+} from './store/actions';
+import type { Store } from './store';
+
+function createStorage(initial: T) {
+ const storage: T = initial;
+ return {
+ update(payload: Partial) {
+ Object.assign(storage, payload);
+ },
+ get(key: K): T[K] {
+ return storage[key];
+ },
+ has(key: keyof T) {
+ return !!storage[key];
+ },
+ destroy() {
+ for (const prop of Object.getOwnPropertyNames(storage)) {
+ delete storage[prop];
+ }
+ },
+ };
+}
+
+interface Entities {
+ view?: EditorView;
+ store?: Store;
+}
+type EntityStorage = ReturnType>;
+
+function createInitialState({
+ stateKey,
+ content,
+ editable,
+ version,
+}: {
+ stateKey: string;
+ content: string;
+ editable: boolean;
+ version: number;
+}) {
+ return createEditorState('article', stateKey, content, version, editable);
+}
+
+function modifyTransaction(stateKey: any, viewId: string, state: EditorState, tr: Transaction) {
+ let next = tr;
+ switch (tr.getMeta('uiEvent')) {
+ case 'cut':
+ case 'paste':
+ case 'drop':
+ next = process.modifyTransactionToValidDocState(tr);
+ break;
+ default:
+ break;
+ }
+ return next;
+}
+
+interface InitOpiton {
+ stateKey: string;
+ viewId: string;
+ content: string;
+ disabled?: boolean;
+ autoUnsubscribe?: boolean;
+ version?: number;
+ cssClass?: string;
+}
+
+export function createEditor(store: Store, options: Options) {
+ const entityStorage = createStorage({});
+ const config = {
+ autoUnsubscribe: false,
+ };
+
+ setup(store, options);
+ return {
+ init(
+ dom: HTMLDivElement,
+ { stateKey, autoUnsubscribe, content, disabled, viewId, version = 0, cssClass }: InitOpiton,
+ ) {
+ if (entityStorage.has('view')) {
+ console.warn('View already exists. This happens when you call init() twice.');
+ return;
+ }
+ config.autoUnsubscribe = !!autoUnsubscribe;
+ const initialState = createInitialState({
+ stateKey,
+ content,
+ editable: !disabled,
+ version,
+ });
+
+ const view = createEditorView(
+ dom,
+ initialState,
+ (tr) => {
+ const mtr = modifyTransaction(stateKey, viewId, view.state, tr);
+ const next = view.state.apply(mtr);
+ store.dispatch(updateEditorState(stateKey, viewId, next, tr));
+ // Immidiately update the view.
+ // This is important for properly handling selections.
+ // Cannot use react event loop here.
+
+ view.updateState(next);
+ },
+ { store },
+ );
+ view.dom.id = viewId;
+ view.dom.onfocus = () => {
+ store.dispatch(updateSelectView(viewId));
+ };
+
+ store.dispatch(registerEditorState(stateKey, initialState));
+
+ if (cssClass) view.dom.classList.add(...cssClass.split(' '));
+ // TODO: revive
+ store.dispatch(subscribeView(stateKey, viewId, view));
+ entityStorage.update({ view, store });
+ },
+ destroy() {
+ if (config.autoUnsubscribe) {
+ // TODO: revive
+ // entityStorage.get('store')?.dispatch(unsubscribeView(stateKey, viewId));
+ }
+ entityStorage.get('view')?.destroy();
+ entityStorage.destroy();
+ },
+ getView() {
+ return entityStorage.get('view');
+ },
+ };
+}
+
+export type Editor = ReturnType;
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index 19608d11..95c1f768 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -12,4 +12,11 @@ export type { Options } from './connect';
export { runtime, collab };
export { isEditable, setEditable } from './prosemirror/plugins/editable';
-export { createEditorState } from './prosemirror';
+export {
+ createEditorState,
+ deleteNodePM,
+ liftContentOutOfNodePM,
+ updateNodeAttrsPM,
+} from './prosemirror';
+
+export { createEditor } from './editor';
diff --git a/packages/editor/src/prosemirror/index.ts b/packages/editor/src/prosemirror/index.ts
index 8e66938a..2c1ace8f 100644
--- a/packages/editor/src/prosemirror/index.ts
+++ b/packages/editor/src/prosemirror/index.ts
@@ -1,15 +1,16 @@
-import { schemas, fromHTML } from '@curvenote/schema';
+import { schemas, fromHTML, nodeNames } from '@curvenote/schema';
import type { Transaction } from 'prosemirror-state';
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { getSelectedViewId } from '../store/selectors';
-import { store, opts } from '../connect';
+import { store as storeFromOpts, opts } from '../connect';
import views from '../views';
import { isEditable } from './plugins/editable';
import { addLink } from '../store/actions/utils';
import { getPlugins } from './plugins';
import { uploadAndInsertImages } from './plugins/ImagePlaceholder';
import { updateSelectView } from '../store/actions';
+import { createFootnoteViewFactory } from '../views/FootnoteView';
export function createEditorState(
useSchema: schemas.UseSchema,
@@ -17,9 +18,13 @@ export function createEditorState(
content: string,
version: number,
startEditable: boolean,
+ { plugins: extraPlugins = opts.plugins }: { plugins?: any[] } = {},
) {
const schema = schemas.getSchema(useSchema);
- const plugins = getPlugins(useSchema, schema, stateKey, version, startEditable);
+ const plugins = [
+ ...getPlugins(useSchema, schema, stateKey, version, startEditable),
+ ...extraPlugins,
+ ];
let state: EditorState;
try {
const data = JSON.parse(content);
@@ -31,6 +36,7 @@ export function createEditorState(
const doc = fromHTML(content, schema, document, DOMParser);
state = EditorState.create({ doc, plugins });
}
+
return state;
}
@@ -38,6 +44,10 @@ export function createEditorView(
dom: HTMLDivElement,
state: EditorState,
dispatch: (tr: Transaction) => void,
+ {
+ nodeViews: extraNodeViews = opts.nodeViews,
+ store = storeFromOpts,
+ }: { nodeViews?: any; store?: any } = {},
): EditorView {
let shiftKey = false; // https://discuss.prosemirror.net/t/change-transformpasted-behaviour-when-shift-key-is-pressed/949/3
const editorView = new EditorView(
@@ -49,11 +59,12 @@ export function createEditorView(
math: views.MathView,
equation: views.EquationView,
code_block: views.CodeBlockView,
- footnote: views.FootnoteView,
+ footnote: createFootnoteViewFactory(store),
image: views.ImageView,
iframe: views.IFrameView,
link: views.LinkView,
link_block: views.createLinkBlockView,
+ [nodeNames.block]: views.createTopBlockView,
time: views.TimeView,
mention: views.MentionView,
button: views.newWidgetView,
@@ -62,7 +73,7 @@ export function createEditorView(
range: views.newWidgetView,
switch: views.newWidgetView,
variable: views.newWidgetView,
- ...opts.nodeViews,
+ ...extraNodeViews,
},
// This can be set in the middleware `tr.setMeta(editable, false)`
editable: (s) => isEditable(s),
@@ -101,3 +112,5 @@ export function createEditorView(
);
return editorView;
}
+
+export * from './utils';
diff --git a/packages/editor/src/prosemirror/keymap/figure.ts b/packages/editor/src/prosemirror/keymap/figure.ts
index 78da6117..f0f92d9c 100644
--- a/packages/editor/src/prosemirror/keymap/figure.ts
+++ b/packages/editor/src/prosemirror/keymap/figure.ts
@@ -2,7 +2,11 @@ import { nodeNames } from '@curvenote/schema';
import type { Command, Transaction } from 'prosemirror-state';
import type { Schema } from 'prosemirror-model';
import { NodeSelection } from 'prosemirror-state';
-import { findParentNode, findParentNodeOfType } from '@curvenote/prosemirror-utils';
+import {
+ findParentNode,
+ findParentNodeOfType,
+ findParentNodeOfTypeClosestToPos,
+} from '@curvenote/prosemirror-utils';
import { insertParagraphAndSelect } from '../../store/actions/utils';
import type { AddKey } from './utils';
@@ -11,10 +15,18 @@ function selectInsideParent(tr: Transaction, pos: number) {
return tr.setSelection(NodeSelection.create(tr.doc, pos - 1)).scrollIntoView();
}
+// since we are just moving out of figurecaption not moving to the root of doc so we use diff depth between figure and figcaption
+const DEPTH_DIFF_BETWEEN_CAPTION_AND_FIGURE = 1;
const handleEnter: Command = function handleEnter(state, dispatch) {
const { $head } = state.selection;
if ($head.parent.type.name === nodeNames.figure) {
- dispatch?.(insertParagraphAndSelect(state.schema, state.tr, $head.end() + $head.depth));
+ dispatch?.(
+ insertParagraphAndSelect(
+ state.schema,
+ state.tr,
+ $head.end() + DEPTH_DIFF_BETWEEN_CAPTION_AND_FIGURE,
+ ),
+ );
return true;
}
if ($head.parent.type.name !== nodeNames.figcaption) return false;
@@ -27,12 +39,24 @@ const handleEnter: Command = function handleEnter(state, dispatch) {
dispatch?.(state.tr.deleteRange(from, to));
return true;
}
- if (figure.pos + figure.node.nodeSize === $head.pos + $head.depth) {
- dispatch?.(insertParagraphAndSelect(state.schema, state.tr, $head.end() + $head.depth));
+ if (figure.pos + figure.node.nodeSize === $head.pos + 1) {
+ dispatch?.(
+ insertParagraphAndSelect(
+ state.schema,
+ state.tr,
+ $head.end() + DEPTH_DIFF_BETWEEN_CAPTION_AND_FIGURE,
+ ),
+ );
return true;
}
if (figure.start === $head.pos - 1) {
- dispatch?.(insertParagraphAndSelect(state.schema, state.tr, $head.start() - $head.depth));
+ dispatch?.(
+ insertParagraphAndSelect(
+ state.schema,
+ state.tr,
+ $head.start() - DEPTH_DIFF_BETWEEN_CAPTION_AND_FIGURE,
+ ),
+ );
return true;
}
dispatch?.(selectInsideParent(state.tr, $head.start()));
@@ -123,7 +147,7 @@ export function buildFigureKeymap(schema: Schema, bind: AddKey) {
bind('Shift-Enter', handleEnter);
bind('Mod-Enter', handleEnter);
bind('Enter', handleEnter);
- bind('Backspace', backspaceAfterFigure, backspaceInFigure);
- bind('Delete', deleteBeforeFigure, deleteInFigure);
+ // bind('Backspace', backspaceAfterFigure, backspaceInFigure);
+ // bind('Delete', deleteBeforeFigure, deleteInFigure);
}
}
diff --git a/packages/editor/src/prosemirror/keymap/index.ts b/packages/editor/src/prosemirror/keymap/index.ts
index 8a1fc85b..830819d0 100644
--- a/packages/editor/src/prosemirror/keymap/index.ts
+++ b/packages/editor/src/prosemirror/keymap/index.ts
@@ -13,8 +13,8 @@ import { wrapInList, splitListItem, liftListItem, sinkListItem } from 'prosemirr
import { undo, redo } from 'prosemirror-history';
import { undoInputRule } from 'prosemirror-inputrules';
import type { Schema } from 'prosemirror-model';
-import { createId } from '@curvenote/schema';
-import type { Command } from 'prosemirror-state';
+import { createId, nodeNames } from '@curvenote/schema';
+import type { Command, EditorState } from 'prosemirror-state';
import { store, opts } from '../../connect';
import { focusSelectedEditorView } from '../../store/ui/actions';
import { executeCommand } from '../../store/actions';
@@ -22,6 +22,7 @@ import { buildFigureKeymap } from './figure';
import { CommandNames } from '../../store/suggestion/commands';
import type { AddKey, Keymap } from './utils';
import { createBind, flattenCommandList } from './utils';
+import { findParentNodeOfTypeClosestToPos } from '@curvenote/prosemirror-utils';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -56,6 +57,16 @@ interface CommandOptions {
figureCommands: boolean;
}
+function getNonSplittableNodes(state: EditorState) {
+ return [
+ state.schema.nodes[nodeNames.figcaption],
+ state.schema.nodes[nodeNames.table],
+ state.schema.nodes[nodeNames.aside],
+ state.schema.nodes[nodeNames.code_block],
+ state.schema.nodes[nodeNames.callout],
+ ];
+}
+
function addAllCommands(stateKey: any, schema: Schema, bind: AddKey, options?: CommandOptions) {
bind('Mod-z', undoInputRule, undo);
bind('Mod-Z', redo);
@@ -83,6 +94,27 @@ function addAllCommands(stateKey: any, schema: Schema, bind: AddKey, options?: C
}
if (schema.nodes.blockquote) bind('Ctrl->', wrapIn(schema.nodes.blockquote));
+ console.log('schema', schema.nodes.block);
+
+ const blockNode = schema.nodes.block;
+ if (blockNode) {
+ const command: Command = (state, dispatch, view) => {
+ if (!dispatch || !view) return false;
+ if (findParentNodeOfTypeClosestToPos(state.selection.$from, getNonSplittableNodes(state))) {
+ return false;
+ }
+ const location = findParentNodeOfTypeClosestToPos(
+ state.selection.$from,
+ state.schema.nodes[nodeNames.block],
+ );
+ if (!location) return true;
+ dispatch(state.tr.split(state.selection.$from.pos, location.depth + 1));
+ return true;
+ };
+
+ bind('Mod-Enter', command);
+ if (mac) bind('Ctrl-Enter', command);
+ }
if (schema.nodes.hard_break) {
const br = schema.nodes.hard_break;
const cmd = chainCommands(exitCode, (state, dispatch) => {
@@ -90,9 +122,11 @@ function addAllCommands(stateKey: any, schema: Schema, bind: AddKey, options?: C
dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView());
return true;
});
- bind('Mod-Enter', cmd);
+ if (!blockNode) {
+ bind('Mod-Enter', cmd);
+ if (mac) bind('Ctrl-Enter', cmd);
+ }
bind('Shift-Enter', cmd);
- if (mac) bind('Ctrl-Enter', cmd);
}
if (schema.nodes.list_item) {
diff --git a/packages/editor/src/prosemirror/plugins/comments.ts b/packages/editor/src/prosemirror/plugins/comments.ts
index 0d122405..cb406212 100644
--- a/packages/editor/src/prosemirror/plugins/comments.ts
+++ b/packages/editor/src/prosemirror/plugins/comments.ts
@@ -3,7 +3,7 @@ import { Plugin, PluginKey } from 'prosemirror-state';
import { isNodeSelection } from '@curvenote/prosemirror-utils';
import type { EditorView } from 'prosemirror-view';
import { Decoration, DecorationSet } from 'prosemirror-view';
-import { actions, selectors, store } from 'sidenotes';
+import { actions, selectors, store as storeFromSideNode } from 'sidenotes';
import { opts } from '../../connect';
export interface CommentState {
@@ -75,7 +75,8 @@ const reducer = (
}
};
-const getCommentsPlugin = (): Plugin => {
+const getCommentsPlugin = (store = storeFromSideNode): Plugin => {
+ console.log('COMMENTS PLUGIN init', store);
const commentsPlugin: Plugin = new Plugin({
key,
state: {
diff --git a/packages/editor/src/prosemirror/plugins/index.ts b/packages/editor/src/prosemirror/plugins/index.ts
index b55a044c..f92b0ee9 100644
--- a/packages/editor/src/prosemirror/plugins/index.ts
+++ b/packages/editor/src/prosemirror/plugins/index.ts
@@ -14,12 +14,13 @@ import type { Trigger } from 'prosemirror-autocomplete';
import { buildBasicKeymap, buildCommentKeymap, buildKeymap, captureTab } from '../keymap';
import inputrules from '../inputrules';
-import { store } from '../../connect';
+import { opts, store as storeFromConnect } from '../../connect';
import { editablePlugin } from './editable';
import { handleSuggestion } from '../../store/suggestion/actions';
import commentsPlugin from './comments';
import { getImagePlaceholderPlugin } from './ImagePlaceholder';
import getPromptPlugin from './prompts';
+import type { Store } from '../../store';
function tablesPlugins(schema: Schema) {
// Don't add plugins if they are not in the schema
@@ -85,7 +86,7 @@ export function getPlugins(
...autocomplete({
triggers: getTriggers(schema, true),
reducer(action) {
- return store.dispatch(handleSuggestion(action));
+ return storeFromConnect.dispatch(handleSuggestion(action));
},
}),
...inputrules(schema),
@@ -101,7 +102,7 @@ export function getPlugins(
...autocomplete({
triggers: getTriggers(schema, false),
reducer(action) {
- return store.dispatch(handleSuggestion(action));
+ return storeFromConnect.dispatch(handleSuggestion(action));
},
}),
getImagePlaceholderPlugin(),
@@ -117,10 +118,10 @@ export function getPlugins(
];
}
-export function getInlinePlugins(schema: Schema): Plugin[] {
+export function getInlinePlugins(schema: Schema, { store }: { store: Store }): Plugin[] {
return [
editablePlugin(true),
- commentsPlugin(),
+ commentsPlugin(store),
...inputrules(schema),
keymap(buildBasicKeymap(schema)),
keymap(baseKeymap),
diff --git a/packages/editor/src/prosemirror/utils.ts b/packages/editor/src/prosemirror/utils.ts
new file mode 100644
index 00000000..544f5b17
--- /dev/null
+++ b/packages/editor/src/prosemirror/utils.ts
@@ -0,0 +1,29 @@
+import type { ContentNodeWithPos } from '@curvenote/prosemirror-utils';
+import { NodeRange } from 'prosemirror-model';
+import { liftTarget } from 'prosemirror-transform';
+import type { EditorView } from 'prosemirror-view';
+
+export function deleteNodePM(view: EditorView, node: Pick) {
+ return view.dispatch(view.state.tr.delete(node.pos, node.pos + node.node.nodeSize));
+}
+export function updateNodeAttrsPM(
+ view: EditorView,
+ node: Pick,
+ attrs: { [index: string]: any },
+) {
+ const tr = view.state.tr.setNodeMarkup(node.pos, undefined, { ...node.node.attrs, ...attrs });
+ view.dispatch(tr);
+}
+export function liftContentOutOfNodePM(
+ view: EditorView,
+ node: Pick,
+) {
+ const editorState = view.state;
+ const $from = editorState.doc.resolve(node.pos + 1);
+ const $to = editorState.doc.resolve(node.pos + node.node.nodeSize - 1);
+ const range = new NodeRange($from, $to, $from.depth);
+ const target = liftTarget(range);
+ if (target == null) return false;
+ const tr = editorState.tr.lift(range, target);
+ view.dispatch(tr);
+}
diff --git a/packages/editor/src/store/actions/editor.ts b/packages/editor/src/store/actions/editor.ts
index 5925a4f3..13fc31fa 100644
--- a/packages/editor/src/store/actions/editor.ts
+++ b/packages/editor/src/store/actions/editor.ts
@@ -78,7 +78,7 @@ export function liftContentOutOfNode(
if (editorState == null) return false;
const $from = editorState.doc.resolve(node.pos + 1);
const $to = editorState.doc.resolve(node.pos + node.node.nodeSize - 1);
- const range = new NodeRange($from, $to, 1);
+ const range = new NodeRange($from, $to, $from.depth);
const target = liftTarget(range);
if (target == null) return false;
const tr = editorState.tr.lift(range, target);
diff --git a/packages/editor/src/store/state/actions.ts b/packages/editor/src/store/state/actions.ts
index ffae4e95..43a053b0 100644
--- a/packages/editor/src/store/state/actions.ts
+++ b/packages/editor/src/store/state/actions.ts
@@ -5,6 +5,7 @@ import type { schemas } from '@curvenote/schema';
import { process } from '@curvenote/schema';
import type { EditorActionTypes } from './types';
import {
+ REGISTER_EDITOR_STATE,
UPDATE_EDITOR_STATE,
INIT_EDITOR_STATE,
SUBSCRIBE_EDITOR_VIEW,
@@ -19,6 +20,18 @@ import { opts } from '../../connect';
import type { LinkType } from '../../components/types';
import { getLinkBoundsIfTheyExist } from '../actions/utils';
+export function registerEditorState(stateKey: any, state: EditorState): EditorActionTypes {
+ const stateId = opts.transformKeyToId(stateKey);
+ if (stateId == null) throw new Error('Must have a state ID');
+ return {
+ type: REGISTER_EDITOR_STATE,
+ payload: {
+ stateKey,
+ stateId,
+ state,
+ },
+ };
+}
export function initEditorState(
useSchema: schemas.UseSchema,
stateKey: any,
diff --git a/packages/editor/src/store/state/reducers.ts b/packages/editor/src/store/state/reducers.ts
index b981be9d..79f4c686 100644
--- a/packages/editor/src/store/state/reducers.ts
+++ b/packages/editor/src/store/state/reducers.ts
@@ -1,6 +1,7 @@
import { process } from '@curvenote/schema';
import type { EditorActionTypes, EditorsState } from './types';
import {
+ REGISTER_EDITOR_STATE,
INIT_EDITOR_STATE,
UPDATE_EDITOR_STATE,
SUBSCRIBE_EDITOR_VIEW,
@@ -19,6 +20,23 @@ export const initialState: EditorsState = {
// eslint-disable-next-line @typescript-eslint/default-param-last
const editorReducer = (state = initialState, action: EditorActionTypes): EditorsState => {
switch (action.type) {
+ case REGISTER_EDITOR_STATE: {
+ const { stateKey, stateId, state: editorState } = action.payload;
+ if (state.editors[stateId] !== undefined) return state;
+ const counts = process.countState(editorState);
+ return {
+ ...state,
+ editors: {
+ ...state.editors,
+ [stateId]: {
+ state: editorState,
+ viewIds: [],
+ key: stateKey,
+ counts,
+ },
+ },
+ };
+ }
case INIT_EDITOR_STATE: {
const { useSchema, stateKey, stateId, content, editable, version } = action.payload;
if (state.editors[stateId] !== undefined) return state;
diff --git a/packages/editor/src/store/state/types.ts b/packages/editor/src/store/state/types.ts
index 8cc8693f..51720119 100644
--- a/packages/editor/src/store/state/types.ts
+++ b/packages/editor/src/store/state/types.ts
@@ -2,6 +2,7 @@ import type { EditorState, Transaction } from 'prosemirror-state';
import type { EditorView } from 'prosemirror-view';
import type { types, schemas } from '@curvenote/schema';
+export const REGISTER_EDITOR_STATE = 'REGISTER_EDITOR_STATE';
export const INIT_EDITOR_STATE = 'INIT_EDITOR_STATE';
export const SUBSCRIBE_EDITOR_VIEW = 'SUBSCRIBE_EDITOR_VIEW';
export const UNSUBSCRIBE_EDITOR_VIEW = 'UNSUBSCRIBE_EDITOR_VIEW';
@@ -40,6 +41,15 @@ export interface InitEditorState {
};
}
+export interface RegisterEditorState {
+ type: typeof REGISTER_EDITOR_STATE;
+ payload: {
+ stateKey: string;
+ stateId: string;
+ state: EditorState;
+ };
+}
+
export interface UpdateEditorState {
type: typeof UPDATE_EDITOR_STATE;
payload: {
@@ -85,6 +95,7 @@ export interface ResetEditorAndViews {
export type EditorActionTypes =
| InitEditorState
+ | RegisterEditorState
| UpdateEditorState
| SubscribeEditorView
| UnsubscribeEditorView
diff --git a/packages/editor/src/store/ui/actions.ts b/packages/editor/src/store/ui/actions.ts
index 90f624a9..d314fef3 100644
--- a/packages/editor/src/store/ui/actions.ts
+++ b/packages/editor/src/store/ui/actions.ts
@@ -3,12 +3,12 @@ import type { InlineSelection, UIActionTypes } from './types';
import { SELECT_EDITOR_VIEW, INLINE_SELECTION, SelectionKinds } from './types';
import type { AppThunk } from '../types';
import { getEditorUI, getInlineActionKind, getSelectedEditorAndViews } from './selectors';
-import { getEditorView } from '../state/selectors';
+import { selectEditorViewState } from '../state/selectors';
import { getSelectionKind } from './utils';
export function updateSelectView(viewId: string | null): AppThunk {
return (dispatch, getState) => {
- const { stateId } = getEditorView(getState(), viewId);
+ const { stateId } = selectEditorViewState(getState(), viewId);
dispatch({
type: SELECT_EDITOR_VIEW,
payload: { stateId, viewId },
@@ -23,7 +23,7 @@ export const selectEditorView = updateSelectView;
export function focusEditorView(viewId: string | null, focused: boolean): AppThunk {
return (dispatch, getState) => {
- const { view } = getEditorView(getState(), viewId);
+ const { view } = selectEditorViewState(getState(), viewId);
if (!view) return;
if (focused) {
view.focus();
diff --git a/packages/editor/src/store/ui/reducers.ts b/packages/editor/src/store/ui/reducers.ts
index 48921f0b..ca173ca7 100644
--- a/packages/editor/src/store/ui/reducers.ts
+++ b/packages/editor/src/store/ui/reducers.ts
@@ -5,6 +5,7 @@ export const initialState: UIState = {
stateId: null,
viewId: null,
selection: null,
+ selectedBlock: null,
};
// eslint-disable-next-line @typescript-eslint/default-param-last
diff --git a/packages/editor/src/store/ui/selectors.ts b/packages/editor/src/store/ui/selectors.ts
index f2479810..8c854af4 100644
--- a/packages/editor/src/store/ui/selectors.ts
+++ b/packages/editor/src/store/ui/selectors.ts
@@ -17,6 +17,7 @@ export const getEditorUIStateAndViewIds: (state: State) => {
export function isInlineActionOpen(state: State) {
return state.editor.ui.selection != null;
}
+
export function getInlineActionAnchorEl(state: State) {
return state.editor.ui.selection?.anchorEl ?? null;
}
diff --git a/packages/editor/src/store/ui/types.ts b/packages/editor/src/store/ui/types.ts
index df3a69a6..347634b1 100644
--- a/packages/editor/src/store/ui/types.ts
+++ b/packages/editor/src/store/ui/types.ts
@@ -31,6 +31,7 @@ export type UIState = {
stateId: string | null;
viewId: string | null;
selection: InlineSelection | null;
+ selectedBlock: string | null;
};
export interface SelectEditorViewAction {
diff --git a/packages/editor/src/store/ui/utils.ts b/packages/editor/src/store/ui/utils.ts
index 7b1b6aae..fc9a6571 100644
--- a/packages/editor/src/store/ui/utils.ts
+++ b/packages/editor/src/store/ui/utils.ts
@@ -1,14 +1,12 @@
import type { NodeSelection, EditorState, Selection } from 'prosemirror-state';
import type { Node } from 'prosemirror-model';
-import { findParentNode, isNodeSelection } from '@curvenote/prosemirror-utils';
+import { findParentNode } from '@curvenote/prosemirror-utils';
import { nodeNames } from '@curvenote/schema';
import { getLinkBoundsIfTheyExist } from '../actions/utils';
import { SelectionKinds } from './types';
-export function getNodeFromSelection(selection?: Selection) {
- if (!selection || !isNodeSelection(selection)) return null;
- const nodeSelection = selection as NodeSelection;
- return nodeSelection.node;
+export function getNodeFromSelection(selection?: Selection): Node | undefined {
+ return (selection as NodeSelection)?.node ?? undefined;
}
export function getNodeIfSelected(state: EditorState | null, nodeName?: nodeNames) {
diff --git a/packages/editor/src/views/FootnoteView.tsx b/packages/editor/src/views/FootnoteView.tsx
index 11f99499..f830209c 100644
--- a/packages/editor/src/views/FootnoteView.tsx
+++ b/packages/editor/src/views/FootnoteView.tsx
@@ -1,14 +1,16 @@
-import { Node } from 'prosemirror-model';
+import type { Node } from 'prosemirror-model';
import { StepMap } from 'prosemirror-transform';
import { keymap } from 'prosemirror-keymap';
import { undo, redo } from 'prosemirror-history';
-import { Transaction, EditorState, TextSelection } from 'prosemirror-state';
+import type { Transaction } from 'prosemirror-state';
+import { EditorState, TextSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { chainCommands, deleteSelection, newlineInCode } from 'prosemirror-commands';
import { isEditable } from '../prosemirror/plugins/editable';
import { getInlinePlugins } from '../prosemirror/plugins';
import { MathView } from './MathView';
-import { GetPos } from './types';
+import type { GetPos } from './types';
+import type { Store } from '../store';
class FootnoteNodeView {
// The node's representation in the editor (empty, for now)
@@ -25,7 +27,7 @@ class FootnoteNodeView {
getPos: GetPos;
- constructor(node: Node, view: EditorView, getPos: GetPos) {
+ constructor(node: Node, view: EditorView, getPos: GetPos, store: Store) {
this.node = node;
this.outerView = view;
this.getPos = getPos;
@@ -54,7 +56,7 @@ class FootnoteNodeView {
state: EditorState.create({
doc: this.node,
plugins: [
- ...getInlinePlugins(this.outerView.state.schema),
+ ...getInlinePlugins(this.outerView.state.schema, { store }),
keymap({
'Mod-a': () => {
const { doc, tr } = this.innerView.state;
@@ -179,6 +181,8 @@ class FootnoteNodeView {
}
}
-export function FootnoteView(node: Node, view: EditorView, getPos: GetPos) {
- return new FootnoteNodeView(node, view, getPos);
+export function createFootnoteViewFactory(store: Store) {
+ return (node: Node, view: EditorView, getPos: GetPos) => {
+ return new FootnoteNodeView(node, view, getPos, store);
+ };
}
diff --git a/packages/editor/src/views/LinkBlockView.tsx b/packages/editor/src/views/LinkBlockView.tsx
index 0fee3b01..dbcdd749 100644
--- a/packages/editor/src/views/LinkBlockView.tsx
+++ b/packages/editor/src/views/LinkBlockView.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { NodeViewProps } from './types';
+import type { NodeViewProps } from './types';
import createNodeView from './NodeView';
function LinkBlock({ node }: NodeViewProps) {
diff --git a/packages/editor/src/views/NodeView.tsx b/packages/editor/src/views/NodeView.tsx
index 30edaf23..683f2a91 100644
--- a/packages/editor/src/views/NodeView.tsx
+++ b/packages/editor/src/views/NodeView.tsx
@@ -1,17 +1,18 @@
import React, { Component } from 'react';
import { render } from 'react-dom';
-import { Node } from 'prosemirror-model';
-import { EditorView } from 'prosemirror-view';
+import type { Node } from 'prosemirror-model';
+import type { EditorView } from 'prosemirror-view';
import { ThemeProvider } from '@material-ui/core';
import { Provider } from 'react-redux';
import { isEditable } from '../prosemirror/plugins/editable';
import { opts, ref } from '../connect';
-import { GetPos, NodeViewProps } from './types';
+import type { GetPos, NodeViewProps } from './types';
export type Options = {
wrapper: 'span' | 'div';
className?: string;
enableSelectionHighlight?: boolean;
+ containsChildren?: boolean;
};
export type ClassWrapperProps = {
@@ -55,6 +56,8 @@ class ClassWrapper extends Component {
export class ReactWrapper {
dom: HTMLElement;
+ contentDOM?: HTMLElement | null;
+
node: Node;
view: EditorView;
@@ -76,8 +79,12 @@ export class ReactWrapper {
this.getPos = getPos;
this.isSelectionHighlightEnabled = !!options.enableSelectionHighlight;
this.dom = document.createElement(options.wrapper);
+ if (options.containsChildren) {
+ this.contentDOM = this.dom;
+ }
if (options.className) this.dom.classList.add(options.className);
+ console.log('this', this);
render(
@@ -128,7 +135,7 @@ export class ReactWrapper {
function createNodeView(
Editor: React.FunctionComponent,
- options: Options = { wrapper: 'div' },
+ options: Options = { wrapper: 'div', containsChildren: false },
) {
return (node: Node, view: EditorView, getPos: boolean | GetPos) =>
new ReactWrapper(Editor, { node, view, getPos: getPos as GetPos }, options);
diff --git a/packages/editor/src/views/block/BlockNodeView.tsx b/packages/editor/src/views/block/BlockNodeView.tsx
new file mode 100644
index 00000000..9159e0c5
--- /dev/null
+++ b/packages/editor/src/views/block/BlockNodeView.tsx
@@ -0,0 +1,496 @@
+import React, { useContext, useMemo } from 'react';
+import { styled, keyframes } from '@stitches/react';
+import { violet, mauve, blackA, whiteA } from '@radix-ui/colors';
+import { HamburgerMenuIcon, ChevronRightIcon } from '@radix-ui/react-icons';
+import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
+
+import type { Node } from 'prosemirror-model';
+import { render } from 'react-dom';
+import type { NodeView, EditorView, Decoration } from 'prosemirror-view';
+import { isEditable } from '../../prosemirror/plugins/editable';
+import type { GetPos } from '../types';
+import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { nodeNames, toTex, toMarkdown } from '@curvenote/schema';
+import classNames from 'classnames';
+import { v4 } from 'uuid';
+import { ProsemirrorContext } from './prosemirrorProvider';
+
+// TODO: move to shared util?
+function copyToClipboard(text: string) {
+ return new Promise((res, rej) => {
+ navigator.clipboard.writeText(text).then(
+ () => {
+ res();
+ },
+ (err) => {
+ rej(err);
+ },
+ );
+ });
+}
+
+function moveBlock(view: EditorView, node: Node, up: boolean) {
+ const { nodeBefore, pos: fromPos } = view.state.selection.$from;
+ const { pos: toPos, nodeAfter } = view.state.selection.$to;
+
+ if ((!nodeBefore && up) || (!nodeAfter && !up)) return;
+
+ const prevPos = nodeBefore ? fromPos - nodeBefore?.content.size - 2 : 0;
+ const nextPos = nodeAfter ? toPos + nodeAfter?.content.size + 2 : view.state.doc.content.size - 1;
+ const destinationPos = up ? prevPos : nextPos;
+
+ const transaction = view.state.tr.insert(destinationPos, node).deleteSelection();
+ view.dispatch(transaction);
+ console.log('moveblock', {
+ up,
+ destinationPos,
+ selection: view.state.selection,
+ selecting: up ? destinationPos : destinationPos - (node?.content.size ?? 0) - 2,
+ });
+ view.dispatch(
+ view.state.tr
+ .setSelection(
+ new NodeSelection(
+ view.state.doc.resolve(
+ up ? destinationPos : destinationPos - (node?.content.size ?? 0) - 2,
+ ),
+ ),
+ )
+ .scrollIntoView(),
+ );
+}
+
+function addBlock(view: EditorView, node: Node, getPos: GetPos, before: boolean) {
+ const blockPos = getPos();
+ const { state, dispatch } = view;
+ const blockNode = state.schema.nodes[nodeNames.block];
+ // create a new node before pos
+ const paragraph = state.schema.nodes.paragraph.createAndFill({}) as Node;
+ const newNode = blockNode.createAndFill({ id: v4() }, [paragraph]) as Node;
+
+ const tr = before
+ ? state.tr.insert(blockPos, newNode)
+ : state.tr.insert(blockPos + node.content.size + 1, newNode);
+
+ dispatch(tr);
+
+ if (before) {
+ const resolvedPos = view.state.tr.doc.resolve(getPos() - 2);
+ view.dispatch(view.state.tr.setSelection(new TextSelection(resolvedPos)));
+ } else {
+ const resolvedPos = view.state.tr.doc.resolve(getPos() + node.content.size + 3);
+ view.dispatch(view.state.tr.setSelection(new TextSelection(resolvedPos)));
+ }
+
+ view.focus();
+}
+
+const slideUpAndFade = keyframes({
+ '0%': { opacity: 0, transform: 'translateY(2px)' },
+ '100%': { opacity: 1, transform: 'translateY(0)' },
+});
+
+const slideRightAndFade = keyframes({
+ '0%': { opacity: 0, transform: 'translateX(-2px)' },
+ '100%': { opacity: 1, transform: 'translateX(0)' },
+});
+
+const slideDownAndFade = keyframes({
+ '0%': { opacity: 0, transform: 'translateY(-2px)' },
+ '100%': { opacity: 1, transform: 'translateY(0)' },
+});
+
+const slideLeftAndFade = keyframes({
+ '0%': { opacity: 0, transform: 'translateX(2px)' },
+ '100%': { opacity: 1, transform: 'translateX(0)' },
+});
+
+const contentStyles = {
+ minWidth: 220,
+ backgroundColor: 'white',
+ borderRadius: 6,
+ padding: 5,
+ boxShadow:
+ '0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
+ '@media (prefers-reduced-motion: no-preference)': {
+ animationDuration: '400ms',
+ animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
+ willChange: 'transform, opacity',
+ '&[data-state="open"]': {
+ '&[data-side="top"]': { animationName: slideDownAndFade },
+ '&[data-side="right"]': { animationName: slideLeftAndFade },
+ '&[data-side="bottom"]': { animationName: slideUpAndFade },
+ '&[data-side="left"]': { animationName: slideRightAndFade },
+ },
+ },
+};
+
+const StyledContent = styled(DropdownMenuPrimitive.Content, { ...contentStyles });
+
+const StyledArrow = styled(DropdownMenuPrimitive.Arrow, {
+ fill: 'white',
+});
+
+function Content({ children, ...props }: any) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+
+const StyledSubContent = styled(DropdownMenuPrimitive.SubContent, { ...contentStyles });
+
+function SubContent(props: any) {
+ return (
+
+
+
+ );
+}
+
+const itemStyles = {
+ all: 'unset',
+ fontSize: 13,
+ cursor: 'pointer',
+ lineHeight: 1,
+ color: violet.violet11,
+ borderRadius: 3,
+ display: 'flex',
+ alignItems: 'center',
+ height: 25,
+ padding: '0 5px',
+ position: 'relative',
+ paddingLeft: 25,
+ userSelect: 'none',
+
+ '&[data-disabled]': {
+ color: mauve.mauve8,
+ pointerEvents: 'none',
+ },
+
+ '&[data-highlighted]': {
+ backgroundColor: violet.violet9,
+ color: violet.violet1,
+ },
+};
+
+const StyledItem = styled(DropdownMenuPrimitive.Item, { ...itemStyles });
+const StyledCheckboxItem = styled(DropdownMenuPrimitive.CheckboxItem, { ...itemStyles });
+const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, { ...itemStyles });
+const StyledSubTrigger = styled(DropdownMenuPrimitive.SubTrigger, {
+ '&[data-state="open"]': {
+ backgroundColor: violet.violet4,
+ color: violet.violet11,
+ },
+ ...itemStyles,
+});
+
+const StyledLabel = styled(DropdownMenuPrimitive.Label, {
+ paddingLeft: 25,
+ fontSize: 12,
+ lineHeight: '25px',
+ color: mauve.mauve11,
+});
+
+const StyledSeparator = styled(DropdownMenuPrimitive.Separator, {
+ height: 1,
+ backgroundColor: violet.violet6,
+ margin: 5,
+});
+
+const StyledItemIndicator = styled(DropdownMenuPrimitive.ItemIndicator, {
+ position: 'absolute',
+ left: 0,
+ width: 25,
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+});
+
+// Exports
+export const DropdownMenu = styled(DropdownMenuPrimitive.Root, {});
+export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
+export const DropdownMenuContent = Content;
+export const DropdownMenuItem = StyledItem;
+export const DropdownMenuCheckboxItem = StyledCheckboxItem;
+export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
+export const DropdownMenuRadioItem = StyledRadioItem;
+export const DropdownMenuItemIndicator = StyledItemIndicator;
+export const DropdownMenuLabel = StyledLabel;
+export const DropdownMenuSeparator = StyledSeparator;
+export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
+export const DropdownMenuSubTrigger = StyledSubTrigger;
+export const DropdownMenuSubContent = SubContent;
+
+// Your app...
+const Box = styled('div', {});
+
+const RightSlot = styled('div', {
+ marginLeft: 'auto',
+ paddingLeft: 20,
+ color: mauve.mauve11,
+ '[data-highlighted] > &': { color: 'white' },
+ '[data-disabled] &': { color: mauve.mauve8 },
+});
+
+const IconButton = styled('button', {
+ all: 'unset',
+ fontFamily: 'inherit',
+ borderRadius: '100%',
+ height: 35,
+ width: 35,
+ display: 'inline-flex',
+ cursor: 'pointer',
+ alignItems: 'center',
+ justifyContent: 'center',
+ color: whiteA.whiteA1,
+ backgroundColor: 'transparent',
+ // boxShadow: `0 2px 10px ${blackA.blackA7}`,
+ '&:hover': { backgroundColor: violet.violet3 },
+ // '&:focus': { boxShadow: `0 0 0 2px black` },
+});
+
+export const DropdownMenuDemo = ({ buttonRef }: { buttonRef: any }) => {
+ const { viewCtx, nodeCtx } = useContext(ProsemirrorContext);
+ if (!viewCtx || !nodeCtx) return null;
+
+ const { view, state } = viewCtx;
+ const { getPos, node } = nodeCtx;
+ return (
+
+ {
+ if (opened) {
+ view.dispatch(
+ view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(getPos()))),
+ );
+ }
+ }}
+ >
+
+
+
+
+
+
+
+ {
+ moveBlock(view, node, true);
+ }}
+ >
+ Move Up
+ ⌥+↑
+
+ {
+ moveBlock(view, node, false);
+ }}
+ >
+ Move Down ⌥+↓
+
+
+
+
+ Copy
+
+
+
+
+
+
+ Copy Block⌘+C
+
+ {
+ copyToClipboard(toMarkdown(node));
+ }}
+ >
+ Copy as Markdown
+
+ {
+ copyToClipboard(toTex(node));
+ }}
+ >
+ Copy as Latex
+
+
+
+
+
+
+
+ Remove {'\u232b'}
+
+
+
+
+ );
+};
+
+function BlockControlMenu({ buttonRef }: { buttonRef: React.RefObject }) {
+ return (
+
+
+
+ );
+}
+
+type Props = {
+ selected?: boolean;
+ toggleBtnRef: React.RefObject;
+ blockControlRef: React.RefObject;
+};
+
+function FancyControl({ toggleBtnRef: ref, blockControlRef, selected }: Props) {
+ const { viewCtx, nodeCtx } = useContext(ProsemirrorContext);
+ if (!viewCtx || !nodeCtx) return null;
+ return (
+
+ {
+ addBlock(viewCtx.view, nodeCtx.node, nodeCtx.getPos, true);
+ }}
+ >
+ +
+
+
+ {
+ addBlock(viewCtx.view, nodeCtx.node, nodeCtx.getPos, false);
+ }}
+ >
+ +
+
+
+ );
+}
+
+function FancyBlockControls({
+ view,
+ node,
+ getPos,
+ selected,
+ toggleBtnRef: ref,
+ blockControlRef,
+}: { view: EditorView; node: Node; getPos: () => number } & Props) {
+ const viewCtx = useMemo(() => ({ view, state: view.state }), [view, view.state]);
+ const nodeCtx = useMemo(() => ({ node, getPos }), [node, getPos]);
+ return (
+ // we bring this back if needed
+
+
+
+ );
+}
+
+function isSelected(decorations: readonly Decoration[]) {
+ return !!decorations.find((deco) => {
+ return (deco as any).type.attrs.selected === 'true'; // decorations here has type passed from selected decoration
+ });
+}
+
+class BlockNodeView implements NodeView {
+ // The node's representation in the editor (empty, for now)
+ dom: HTMLDivElement;
+ contentDOM: HTMLDivElement;
+ blockControlsContainer: HTMLDivElement;
+
+ node: Node;
+
+ view: EditorView;
+
+ getPos: GetPos;
+
+ dotMenuToggleBtn: React.RefObject;
+ blockControlRef: React.RefObject;
+
+ constructor(node: Node, view: EditorView, getPos: GetPos, decorations: readonly Decoration[]) {
+ this.node = node;
+ this.view = view;
+ this.getPos = getPos;
+ this.dom = document.createElement('div');
+ this.dom.setAttribute('id', node.attrs.id);
+ this.dotMenuToggleBtn = React.createRef();
+ this.blockControlRef = React.createRef();
+
+ const selected = isSelected(decorations);
+
+ console.log('create new ', node.attrs.id);
+ const blockControls = document.createElement('div');
+ this.blockControlsContainer = blockControls;
+ blockControls.setAttribute('contenteditable', 'false');
+
+ this.renderFansyBlockControls(node, selected);
+ this.dom.appendChild(blockControls);
+
+ const contentContainer = document.createElement('div');
+ this.dom.appendChild(contentContainer);
+
+ this.dom.classList.add('block-node-view');
+ this.contentDOM = contentContainer; // tells prosemirror to render children here
+ }
+
+ deselectNode() {
+ if (!isEditable(this.view.state) || !this.getPos) return;
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+
+ renderFansyBlockControls(node: Node, selected?: boolean) {
+ render(
+ ,
+ this.blockControlsContainer,
+ );
+ }
+
+ update(node: Node, decorations: readonly any[]) {
+ if (!this.view || !this.getPos) return false;
+ if (decorations.length === 0) {
+ this.renderFansyBlockControls(node);
+ return true;
+ }
+
+ const selected = isSelected(decorations);
+
+ this.renderFansyBlockControls(node, selected);
+ return true;
+ }
+
+ selectNode() {
+ if (!isEditable(this.view.state) || !this.getPos) return;
+ this.dom.classList.add('ProseMirror-selectednode');
+ }
+
+ ignoreMutation(mutation: MutationRecord) {
+ return (
+ mutation.target === this.dotMenuToggleBtn.current ||
+ mutation.target === this.blockControlRef.current
+ );
+ }
+}
+
+export function createTopBlockView(
+ node: Node,
+ view: EditorView,
+ getPos: GetPos,
+ decorations: readonly Decoration[],
+) {
+ return new BlockNodeView(node, view, getPos, decorations);
+}
diff --git a/packages/editor/src/views/block/index.ts b/packages/editor/src/views/block/index.ts
new file mode 100644
index 00000000..2a210ea0
--- /dev/null
+++ b/packages/editor/src/views/block/index.ts
@@ -0,0 +1 @@
+export { createTopBlockView } from './BlockNodeView';
diff --git a/packages/editor/src/views/block/prosemirrorProvider.ts b/packages/editor/src/views/block/prosemirrorProvider.ts
new file mode 100644
index 00000000..fc8a5343
--- /dev/null
+++ b/packages/editor/src/views/block/prosemirrorProvider.ts
@@ -0,0 +1,9 @@
+import type { Node } from 'prosemirror-model';
+import type { EditorState } from 'prosemirror-state';
+import type { EditorView } from 'prosemirror-view';
+import React from 'react';
+
+export const ProsemirrorContext = React.createContext<{
+ viewCtx?: { view: EditorView; state: EditorState };
+ nodeCtx?: { node: Node; getPos: () => number };
+}>({});
diff --git a/packages/editor/src/views/index.ts b/packages/editor/src/views/index.ts
index d23760cf..cb156bc2 100644
--- a/packages/editor/src/views/index.ts
+++ b/packages/editor/src/views/index.ts
@@ -6,7 +6,8 @@ import { createLinkBlockView } from './LinkBlockView';
import { TimeView } from './TimeView';
import { MentionView } from './Mention';
import { CodeBlockView } from './CodeBlockView';
-import { FootnoteView } from './FootnoteView';
+import { createTopBlockView } from './block';
+import { createFootnoteViewFactory } from './FootnoteView';
import createNodeView from './NodeView';
import WidgetView, { newWidgetView } from './WidgetView';
import { clickSelectFigure } from './utils';
@@ -23,10 +24,11 @@ export default {
ImageView,
IFrameView,
LinkView,
+ createTopBlockView,
createLinkBlockView,
TimeView,
WidgetView,
newWidgetView,
CodeBlockView,
- FootnoteView,
+ createFootnoteViewFactory,
};
diff --git a/packages/editor/styles/index.scss b/packages/editor/styles/index.scss
index 35419cc2..2d23341a 100644
--- a/packages/editor/styles/index.scss
+++ b/packages/editor/styles/index.scss
@@ -29,3 +29,36 @@
display: none !important;
}
}
+
+.block-node-view {
+ padding: 12px 16px;
+ margin: 8px 0px;
+ border-radius: 4px;
+ border: 1px solid black;
+ position: relative;
+ .block-controls{
+ display: none;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ position: absolute;
+ top: 0px;
+ left: -35px;
+ cursor: all-scroll;
+ background-color: gray;
+ color: white;
+
+ }
+ &:hover {
+ .block-controls {
+ display: flex;
+ }
+ }
+
+ .selected-block-control {
+ background-color: #315F98;
+ display: flex;
+ }
+}
+
+
diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json
index 6c9da59b..74f4e5f1 100644
--- a/packages/editor/tsconfig.json
+++ b/packages/editor/tsconfig.json
@@ -8,7 +8,6 @@
"esnext"
],
"outDir": "dist",
- "baseUrl": "./src",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@@ -35,6 +34,7 @@
"demo/**/*"
],
"exclude": [
- "**/*.spec.ts"
+ "**/*.spec.ts",
+ "dist"
]
}
diff --git a/packages/editor/webpack.dev.js b/packages/editor/webpack.dev.js
index db69df88..1d6a7865 100644
--- a/packages/editor/webpack.dev.js
+++ b/packages/editor/webpack.dev.js
@@ -6,5 +6,6 @@ module.exports = merge(common, {
devtool: 'inline-source-map',
devServer: {
port: 3010,
+ historyApiFallback: true,
},
});
diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts
index 8576448b..788ca46a 100644
--- a/packages/schema/src/index.ts
+++ b/packages/schema/src/index.ts
@@ -2,6 +2,7 @@ export * as schemas from './schemas';
export * as Nodes from './nodes';
export * as spec from './spec';
export * as types from './types';
+export { NODE_GROUPS } from './nodes/types';
export * from './utils';
export { markNames, nodeNames } from './types';
diff --git a/packages/schema/src/nodes/aside.ts b/packages/schema/src/nodes/aside.ts
index 9159defe..4348e6e9 100644
--- a/packages/schema/src/nodes/aside.ts
+++ b/packages/schema/src/nodes/aside.ts
@@ -1,30 +1,33 @@
import { createLatexStatement } from '../serialize/tex/utils';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import type { Margin, FlowContent } from '../spec';
-import type { MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup } from './types';
-const aside: MyNodeSpec, Margin> = {
- attrs: {},
- group: NodeGroups.top,
- content: NodeGroups.blockOrHeading,
- toDOM() {
- return ['aside', { class: 'margin' }, 0];
- },
- parseDOM: [
- { tag: 'aside' }, // This is legacy and should be removed!
- { tag: 'aside.margin' },
- ],
- attrsFromMyst() {
- return {};
- },
- toMyst(props) {
- return {
- type: 'margin',
- children: (props.children || []) as FlowContent[],
- };
- },
-};
+export function createAsideNodeSpec(
+ nodeGroup: NodeGroup,
+): MyNodeSpec, Margin> {
+ return {
+ attrs: {},
+ group: nodeGroup.top,
+ content: nodeGroup.blockOrHeading,
+ toDOM() {
+ return ['aside', { class: 'margin' }, 0];
+ },
+ parseDOM: [
+ { tag: 'aside' }, // This is legacy and should be removed!
+ { tag: 'aside.margin' },
+ ],
+ attrsFromMyst() {
+ return {};
+ },
+ toMyst(props) {
+ return {
+ type: 'margin',
+ children: (props.children || []) as FlowContent[],
+ };
+ },
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
state.ensureNewLine();
@@ -44,5 +47,3 @@ export const toTex: TexFormatSerialize = createLatexStatement(
state.renderContent(node);
},
);
-
-export default aside;
diff --git a/packages/schema/src/nodes/basic.ts b/packages/schema/src/nodes/basic.ts
index 7ff00a4c..3939fb54 100644
--- a/packages/schema/src/nodes/basic.ts
+++ b/packages/schema/src/nodes/basic.ts
@@ -14,122 +14,165 @@ import type {
PhrasingContent,
ThematicBreak,
} from '../spec';
-import type { MyNodeSpec, Props } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup, Props } from './types';
+import { ARTICLE_NODE_GROUPS, LEGACY_NODE_GROUPS } from './types';
import { nodeNames } from '../types';
export const doc: NodeSpec = {
- content: `(${NodeGroups.block} | ${NodeGroups.heading} | ${NodeGroups.top})+`,
+ content: `(${LEGACY_NODE_GROUPS.content} | ${LEGACY_NODE_GROUPS.heading} | ${LEGACY_NODE_GROUPS.top})+`,
};
-
-export const docParagraph: NodeSpec = {
- content: 'paragraph',
-};
-
-export const docComment: NodeSpec = {
- content: `(${NodeGroups.block} | ${NodeGroups.heading} | ${nodeNames.equation})+`, // browsers will completely collapse the node when it's empty `+` is necessary
+export const docWithBlock: NodeSpec = {
+ content: `${ARTICLE_NODE_GROUPS.block}+`,
};
+export const block: NodeSpec = {
+ attrs: { id: { default: null } },
+ draggable: true,
+ isolating: true,
+ content: `(${ARTICLE_NODE_GROUPS.content} | ${ARTICLE_NODE_GROUPS.heading} | ${ARTICLE_NODE_GROUPS.top})+`,
+ group: ARTICLE_NODE_GROUPS.block,
+ parseDOM: [
+ {
+ tag: 'div.block',
+ getAttrs(dom) {
+ if (dom instanceof HTMLElement) {
+ return {
+ id: dom.getAttribute('id') || null,
+ };
+ }
+ return {
+ id: null,
+ };
+ },
+ },
+ ],
-export const paragraph: MyNodeSpec = {
- attrs: {},
- content: `${NodeGroups.inline}*`,
- group: NodeGroups.block,
- parseDOM: [{ tag: 'p' }],
- toDOM() {
- return ['p', 0];
+ toDOM({ attrs: { id } }) {
+ return ['div', { class: 'block', id }, 0];
},
- attrsFromMyst: () => ({}),
- toMyst: (props) => ({
- type: 'paragraph',
- children: (props.children || []) as PhrasingContent[],
- }),
};
-export const blockquote: MyNodeSpec = {
- attrs: {},
- content: `${NodeGroups.block}+`,
- group: NodeGroups.block,
- defining: true,
- parseDOM: [{ tag: 'blockquote' }],
- toDOM() {
- return ['blockquote', 0];
- },
- attrsFromMyst: () => ({}),
- toMyst: (props) => ({
- type: 'blockquote',
- children: (props.children || []) as FlowContent[],
- }),
-};
+export function createBasicNodeSpecs(nodeGroup: NodeGroup) {
+ const docParagraph: NodeSpec = {
+ content: 'paragraph',
+ };
+ const docComment: NodeSpec = {
+ content: `(${nodeGroup.content} | ${nodeGroup.heading} | ${nodeNames.equation})+`, // browsers will completely collapse the node when it's empty `+` is necessary
+ };
-/** Horizontal rule */
-export const horizontal_rule: MyNodeSpec = {
- attrs: {},
- group: NodeGroups.block,
- parseDOM: [{ tag: 'hr' }],
- toDOM() {
- return ['hr', { class: 'break' }];
- },
- attrsFromMyst: () => ({}),
- toMyst: (): ThematicBreak => ({ type: 'thematicBreak' }),
-};
+ const paragraph: MyNodeSpec = {
+ attrs: {},
+ content: `${nodeGroup.inline}*`,
+ group: nodeGroup.content,
+ parseDOM: [{ tag: 'p' }],
+ toDOM() {
+ return ['p', 0];
+ },
+ attrsFromMyst: () => ({}),
+ toMyst: (props) => ({
+ type: 'paragraph',
+ children: (props.children || []) as PhrasingContent[],
+ }),
+ };
-export const text: NodeSpec = {
- group: NodeGroups.inline,
-};
+ const blockquote: MyNodeSpec = {
+ attrs: {},
+ content: `${nodeGroup.content}+`,
+ group: nodeGroup.content,
+ defining: true,
+ parseDOM: [{ tag: 'blockquote' }],
+ toDOM() {
+ return ['blockquote', 0];
+ },
+ attrsFromMyst: () => ({}),
+ toMyst: (props) => ({
+ type: 'blockquote',
+ children: (props.children || []) as FlowContent[],
+ }),
+ };
-export const hard_break: MyNodeSpec = {
- attrs: {},
- inline: true,
- group: NodeGroups.inline,
- selectable: false,
- parseDOM: [{ tag: 'br' }],
- toDOM() {
- return ['br'];
- },
- attrsFromMyst: () => ({}),
- toMyst: (): Break => ({ type: 'break' }),
-};
+ /** Horizontal rule */
+ const horizontal_rule: MyNodeSpec = {
+ attrs: {},
+ group: nodeGroup.content,
+ parseDOM: [{ tag: 'hr' }],
+ toDOM() {
+ return ['hr', { class: 'break' }];
+ },
+ attrsFromMyst: () => ({}),
+ toMyst: (): ThematicBreak => ({ type: 'thematicBreak' }),
+ };
-const listNodes = addListNodes(
- OrderedMap.from({}),
- `paragraph ${NodeGroups.block}*`,
- NodeGroups.block,
-) as OrderedMap>;
+ const text: NodeSpec = {
+ group: nodeGroup.inline,
+ };
-export type OrderedListAttrs = {
- order: number;
-};
+ const hard_break: MyNodeSpec = {
+ attrs: {},
+ inline: true,
+ group: nodeGroup.inline,
+ selectable: false,
+ parseDOM: [{ tag: 'br' }],
+ toDOM() {
+ return ['br'];
+ },
+ attrsFromMyst: () => ({}),
+ toMyst: (): Break => ({ type: 'break' }),
+ };
+
+ const listNodes = addListNodes(
+ OrderedMap.from({}),
+ `paragraph ${nodeGroup.content}*`,
+ nodeGroup.content,
+ ) as OrderedMap>;
+
+ const ordered_list = listNodes.get('ordered_list') as MyNodeSpec;
+ ordered_list.attrsFromMyst = (token: GenericNode) => ({ order: token.start || 1 });
+ ordered_list.toMyst = (props: Props) => ({
+ type: 'list',
+ ordered: true,
+ // This feels like it should be `start: props.order`, but it
+ // is in fact correct as is since we are grabbing these props
+ // off the HTML in `convertToMdast`, not the prosemirror node
+ // https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.js#L17
+ start: props.start || undefined,
+ children: (props.children || []) as ListContent[],
+ });
-export const ordered_list = listNodes.get('ordered_list') as MyNodeSpec;
-ordered_list.attrsFromMyst = (token: GenericNode) => ({ order: token.start || 1 });
-ordered_list.toMyst = (props: Props) => ({
- type: 'list',
- ordered: true,
- // This feels like it should be `start: props.order`, but it
- // is in fact correct as is since we are grabbing these props
- // off the HTML in `convertToMdast`, not the prosemirror node
- // https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.js#L17
- start: props.start || undefined,
- children: (props.children || []) as ListContent[],
-});
+ const bullet_list = listNodes.get('bullet_list') as MyNodeSpec;
+ bullet_list.attrsFromMyst = () => ({});
+ bullet_list.toMyst = (props: Props) => ({
+ type: 'list',
+ ordered: false,
+ children: (props.children || []) as ListContent[],
+ });
-export const bullet_list = listNodes.get('bullet_list') as MyNodeSpec;
-bullet_list.attrsFromMyst = () => ({});
-bullet_list.toMyst = (props: Props) => ({
- type: 'list',
- ordered: false,
- children: (props.children || []) as ListContent[],
-});
+ const list_item = listNodes.get('list_item') as MyNodeSpec;
+ list_item.attrsFromMyst = () => ({});
+ list_item.toMyst = (props: Props) => {
+ let { children } = props;
+ if (children && children.length === 1 && children[0].type === 'paragraph') {
+ children = children[0].children;
+ }
+ return {
+ type: 'listItem',
+ children: (children || []) as PhrasingContent[],
+ };
+ };
-export const list_item = listNodes.get('list_item') as MyNodeSpec;
-list_item.attrsFromMyst = () => ({});
-list_item.toMyst = (props: Props) => {
- let { children } = props;
- if (children && children.length === 1 && children[0].type === 'paragraph') {
- children = children[0].children;
- }
return {
- type: 'listItem',
- children: (children || []) as PhrasingContent[],
+ docParagraph,
+ docComment,
+ paragraph,
+ blockquote,
+ horizontal_rule,
+ text,
+ hard_break,
+ listNodes,
+ ordered_list,
+ bullet_list,
+ list_item,
};
+}
+export type OrderedListAttrs = {
+ order: number;
};
diff --git a/packages/schema/src/nodes/callout.ts b/packages/schema/src/nodes/callout.ts
index 2eb93995..5e0a18f9 100644
--- a/packages/schema/src/nodes/callout.ts
+++ b/packages/schema/src/nodes/callout.ts
@@ -1,8 +1,7 @@
import type { Admonition, FlowContent } from '../spec';
import type { MdFormatSerialize } from '../serialize/types';
import { createLatexStatement } from '../serialize/tex/utils';
-import type { MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup } from './types';
export enum CalloutKinds {
'active' = 'active',
@@ -64,45 +63,47 @@ export function admonitionToCalloutKind(kind?: string): CalloutKinds {
}
}
-const callout: MyNodeSpec = {
- group: NodeGroups.top,
- content: NodeGroups.blockOrEquationOrHeading,
- attrs: {
- kind: { default: CalloutKinds.info },
- },
- toDOM(node) {
- return ['aside', { class: `callout ${node.attrs.kind}` }, 0];
- },
- parseDOM: [
- {
- tag: 'aside.callout',
- getAttrs(dom: any) {
- if (dom.classList.contains(CalloutKinds.active)) return { kind: CalloutKinds.active };
- if (dom.classList.contains(CalloutKinds.success)) return { kind: CalloutKinds.success };
- if (dom.classList.contains(CalloutKinds.info)) return { kind: CalloutKinds.info };
- if (dom.classList.contains(CalloutKinds.warning)) return { kind: CalloutKinds.warning };
- if (dom.classList.contains(CalloutKinds.danger)) return { kind: CalloutKinds.danger };
- return { kind: CalloutKinds.info };
+export function createCalloutNodeSpec(nodeGroup: NodeGroup): MyNodeSpec {
+ return {
+ group: nodeGroup.top,
+ content: nodeGroup.blockOrEquationOrHeading,
+ attrs: {
+ kind: { default: CalloutKinds.info },
+ },
+ toDOM(node) {
+ return ['aside', { class: `callout ${node.attrs.kind}` }, 0];
+ },
+ parseDOM: [
+ {
+ tag: 'aside.callout',
+ getAttrs(dom: any) {
+ if (dom.classList.contains(CalloutKinds.active)) return { kind: CalloutKinds.active };
+ if (dom.classList.contains(CalloutKinds.success)) return { kind: CalloutKinds.success };
+ if (dom.classList.contains(CalloutKinds.info)) return { kind: CalloutKinds.info };
+ if (dom.classList.contains(CalloutKinds.warning)) return { kind: CalloutKinds.warning };
+ if (dom.classList.contains(CalloutKinds.danger)) return { kind: CalloutKinds.danger };
+ return { kind: CalloutKinds.info };
+ },
+ // aside is also parsed, and this is higher priority
+ priority: 60,
},
- // aside is also parsed, and this is higher priority
- priority: 60,
+ ],
+ attrsFromMyst: (token) => ({
+ kind: admonitionToCalloutKind(token.kind),
+ }),
+ toMyst: (props) => {
+ let calloutKind = props.class.split(' ')[1];
+ if (!Object.values(AdmonitionKinds).includes(calloutKind as AdmonitionKinds)) {
+ calloutKind = calloutKindToAdmonition(calloutKind as CalloutKinds);
+ }
+ return {
+ type: 'admonition',
+ kind: calloutKind,
+ children: (props.children || []) as FlowContent[],
+ };
},
- ],
- attrsFromMyst: (token) => ({
- kind: admonitionToCalloutKind(token.kind),
- }),
- toMyst: (props) => {
- let calloutKind = props.class.split(' ')[1];
- if (!Object.values(AdmonitionKinds).includes(calloutKind as AdmonitionKinds)) {
- calloutKind = calloutKindToAdmonition(calloutKind as CalloutKinds);
- }
- return {
- type: 'admonition',
- kind: calloutKind,
- children: (props.children || []) as FlowContent[],
- };
- },
-};
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
state.ensureNewLine();
@@ -124,5 +125,3 @@ export const toTex = createLatexStatement(
state.renderContent(node);
},
);
-
-export default callout;
diff --git a/packages/schema/src/nodes/cite-group.ts b/packages/schema/src/nodes/cite-group.ts
index 94b1c829..f15a4168 100644
--- a/packages/schema/src/nodes/cite-group.ts
+++ b/packages/schema/src/nodes/cite-group.ts
@@ -1,36 +1,37 @@
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
-import type { MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup } from './types';
import type { Cite, CiteGroup } from '../spec';
export type Attrs = Record;
-const citeGroup: MyNodeSpec = {
- attrs: {},
- inline: true,
- atom: true,
- group: NodeGroups.inline,
- marks: '',
- content: `${NodeGroups.cite}+`,
- draggable: true,
- parseDOM: [
- {
- tag: 'cite-group',
- getAttrs() {
- return {};
+export function createCiteGroup(nodeGroup: NodeGroup): MyNodeSpec {
+ return {
+ attrs: {},
+ inline: true,
+ atom: true,
+ group: nodeGroup.inline,
+ marks: '',
+ content: `${nodeGroup.cite}+`,
+ draggable: true,
+ parseDOM: [
+ {
+ tag: 'cite-group',
+ getAttrs() {
+ return {};
+ },
},
+ ],
+ toDOM() {
+ return ['cite-group', 0];
},
- ],
- toDOM() {
- return ['cite-group', 0];
- },
- attrsFromMyst: () => ({}),
- toMyst: (props) => ({
- type: 'citeGroup',
- kind: 'parenthetical',
- children: props.children as Cite[],
- }),
-};
+ attrsFromMyst: () => ({}),
+ toMyst: (props) => ({
+ type: 'citeGroup',
+ kind: 'parenthetical',
+ children: props.children as Cite[],
+ }),
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
state.nextCitationInGroup = node.childCount;
@@ -47,5 +48,3 @@ export const toTex: TexFormatSerialize = (state, node) => {
state.write('}');
state.nextCitationInGroup = 0;
};
-
-export default citeGroup;
diff --git a/packages/schema/src/nodes/cite.ts b/packages/schema/src/nodes/cite.ts
index 5c83538e..d0b93d5c 100644
--- a/packages/schema/src/nodes/cite.ts
+++ b/packages/schema/src/nodes/cite.ts
@@ -1,8 +1,8 @@
import type { GenericNode } from 'mystjs';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import type { Cite, CrossReference } from '../spec';
-import type { MyNodeSpec } from './types';
-import { NodeGroups, ReferenceKind } from './types';
+import type { MyNodeSpec, NodeGroup } from './types';
+import { ReferenceKind } from './types';
import { flattenValues, normalizeLabel } from './utils';
export type Attrs = {
@@ -15,94 +15,98 @@ export type Attrs = {
type Legacy = { inline: undefined };
-const cite: MyNodeSpec = {
- attrs: {
- key: { default: null },
- title: { default: '' },
- kind: { default: null },
- label: { default: null },
- // TODO: This has been renamed to text - need to deprecate
- inline: { default: null },
- text: { default: '' },
- },
- inline: true,
- marks: '',
- group: NodeGroups.inline,
- draggable: true,
- parseDOM: [
- {
- tag: 'cite',
- getAttrs(dom: any) {
- return {
- key:
- dom.getAttribute('key') ??
- dom.getAttribute('data-key') ??
- dom.getAttribute('data-cite'),
- title: dom.getAttribute('title') ?? '',
- kind: dom.getAttribute('kind') || 'cite',
- label: dom.getAttribute('label') || null,
- // inline is for legacy
- inline: undefined,
- // `text` is the rendered text e.g. "Jon et. al, 2020" OR is "Table %s"
- text: dom.getAttribute('inline') ?? dom.textContent ?? '',
- };
- },
+export function createCiteNodeSpec(
+ nodeGroup: NodeGroup,
+): MyNodeSpec {
+ return {
+ attrs: {
+ key: { default: null },
+ title: { default: '' },
+ kind: { default: null },
+ label: { default: null },
+ // TODO: This has been renamed to text - need to deprecate
+ inline: { default: null },
+ text: { default: '' },
},
- ],
- toDOM(node) {
- const { key, kind, text, label, title } = node.attrs as Attrs;
- return [
- 'cite',
+ inline: true,
+ marks: '',
+ group: nodeGroup.inline,
+ draggable: true,
+ parseDOM: [
{
- key: key || undefined,
- kind: kind ?? 'cite',
- title: title || undefined,
- label: label || undefined, // Should we remove this?!?!
+ tag: 'cite',
+ getAttrs(dom: any) {
+ return {
+ key:
+ dom.getAttribute('key') ??
+ dom.getAttribute('data-key') ??
+ dom.getAttribute('data-cite'),
+ title: dom.getAttribute('title') ?? '',
+ kind: dom.getAttribute('kind') || 'cite',
+ label: dom.getAttribute('label') || null,
+ // inline is for legacy
+ inline: undefined,
+ // `text` is the rendered text e.g. "Jon et. al, 2020" OR is "Table %s"
+ text: dom.getAttribute('inline') ?? dom.textContent ?? '',
+ };
+ },
},
- text || '',
- ];
- },
- attrsFromMyst: (node) => {
- if (node.type === 'crossReference') {
- const crossRef = node as CrossReference;
+ ],
+ toDOM(node) {
+ const { key, kind, text, label, title } = node.attrs as Attrs;
+ return [
+ 'cite',
+ {
+ key: key || undefined,
+ kind: kind ?? 'cite',
+ title: title || undefined,
+ label: label || undefined, // Should we remove this?!?!
+ },
+ text || '',
+ ];
+ },
+ attrsFromMyst: (node) => {
+ if (node.type === 'crossReference') {
+ const crossRef = node as CrossReference;
+ return {
+ key: crossRef.identifier ?? null,
+ kind: ReferenceKind.fig, // we loose this information?!
+ label: null,
+ title: null,
+ inline: undefined,
+ text: (node.children?.[0] as GenericNode)?.value || node.label || node.identifier || '',
+ };
+ }
return {
- key: crossRef.identifier ?? null,
- kind: ReferenceKind.fig, // we loose this information?!
+ key: node.identifier ?? null,
+ kind: ReferenceKind.cite,
label: null,
title: null,
inline: undefined,
- text: (node.children?.[0] as GenericNode)?.value || node.label || node.identifier || '',
+ text: flattenValues(node),
};
- }
- return {
- key: node.identifier ?? null,
- kind: ReferenceKind.cite,
- label: null,
- title: null,
- inline: undefined,
- text: flattenValues(node),
- };
- },
- toMyst: (props, options): Cite | CrossReference => {
- if (props.kind === ReferenceKind.cite) {
- const citeKey = options.localizeCitation?.(props.key ?? '') ?? props.key ?? '';
- const { identifier, label } = normalizeLabel(citeKey) ?? {};
+ },
+ toMyst: (props, options): Cite | CrossReference => {
+ if (props.kind === ReferenceKind.cite) {
+ const citeKey = options.localizeCitation?.(props.key ?? '') ?? props.key ?? '';
+ const { identifier, label } = normalizeLabel(citeKey) ?? {};
+ return {
+ type: 'cite',
+ identifier,
+ label,
+ };
+ }
+ const localizedId = options.localizeId?.(props.key ?? '') ?? props.key ?? '';
+ const { identifier, label } = normalizeLabel(localizedId) ?? {};
return {
- type: 'cite',
+ type: 'crossReference',
identifier,
label,
+ children: [{ type: 'text', value: props.text || '' }],
};
- }
- const localizedId = options.localizeId?.(props.key ?? '') ?? props.key ?? '';
- const { identifier, label } = normalizeLabel(localizedId) ?? {};
- return {
- type: 'crossReference',
- identifier,
- label,
- children: [{ type: 'text', value: props.text || '' }],
- };
- },
-};
+ },
+ };
+}
function getPrependedText(kind: ReferenceKind): string {
switch (kind) {
@@ -164,5 +168,3 @@ export const toTex: TexFormatSerialize = (state, node) => {
const id = state.options.localizeId?.(key ?? '') ?? key;
if (id) state.write(`${prepend}\\ref{${id}}`);
};
-
-export default cite;
diff --git a/packages/schema/src/nodes/code.ts b/packages/schema/src/nodes/code.ts
index 46d08cc8..c4df01ab 100644
--- a/packages/schema/src/nodes/code.ts
+++ b/packages/schema/src/nodes/code.ts
@@ -1,8 +1,7 @@
import type { Code } from '../spec';
import { createLatexStatement } from '../serialize/tex/utils';
import type { MdFormatSerialize } from '../serialize/types';
-import type { NumberedNode, MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import type { NumberedNode, MyNodeSpec, NodeGroup } from './types';
import {
convertToBooleanAttribute,
readBooleanDomAttr,
@@ -60,65 +59,67 @@ function langToLanguage(lang?: string): string | undefined {
return lang.toLowerCase();
}
-const code_block: MyNodeSpec = {
- attrs: {
- ...getNumberedDefaultAttrs(),
- language: { default: null },
- linenumbers: { default: false },
- title: { default: '' },
- },
- content: `${NodeGroups.text}*`,
- marks: '',
- group: NodeGroups.block,
- code: true,
- defining: true,
- parseDOM: [
- {
- tag: 'pre',
- preserveWhitespace: 'full',
- getAttrs(dom) {
- return {
- ...getNumberedAttrs(dom),
- language: dom.getAttribute('language') || null,
- linenumbers: readBooleanDomAttr(dom, 'linenumbers'),
- title: dom.getAttribute('title') ?? '',
- };
- },
+export function createCodeBlockSpec(nodeGroup: NodeGroup): MyNodeSpec {
+ return {
+ attrs: {
+ ...getNumberedDefaultAttrs(),
+ language: { default: null },
+ linenumbers: { default: false },
+ title: { default: '' },
},
- ],
- toDOM(node) {
- const { language, title, linenumbers } = node.attrs;
- return [
- 'pre',
+ content: `${nodeGroup.text}*`,
+ marks: '',
+ group: nodeGroup.content,
+ code: true,
+ defining: true,
+ parseDOM: [
{
- ...setNumberedAttrs(node.attrs),
- language,
- title,
- linenumbers: convertToBooleanAttribute(linenumbers),
+ tag: 'pre',
+ preserveWhitespace: 'full',
+ getAttrs(dom) {
+ return {
+ ...getNumberedAttrs(dom),
+ language: dom.getAttribute('language') || null,
+ linenumbers: readBooleanDomAttr(dom, 'linenumbers'),
+ title: dom.getAttribute('title') ?? '',
+ };
+ },
},
- ['code', 0],
- ];
- },
- attrsFromMyst: (token) => ({
- id: token.identifier || null,
- label: token.label || null,
- numbered: false,
- language: langToLanguage(token.lang) || null,
- linenumbers: token.showLineNumbers || false,
- title: '',
- }),
- toMyst: (props) => {
- if (props.children?.length === 1) {
- return {
- type: 'code',
- lang: languageToLang(props.language || undefined),
- showLineNumbers: props.linenumbers || undefined,
- value: props.children[0].value || '',
- };
- }
- throw new Error(`Code block node does not have one child`);
- },
-};
+ ],
+ toDOM(node) {
+ const { language, title, linenumbers } = node.attrs;
+ return [
+ 'pre',
+ {
+ ...setNumberedAttrs(node.attrs),
+ language,
+ title,
+ linenumbers: convertToBooleanAttribute(linenumbers),
+ },
+ ['code', 0],
+ ];
+ },
+ attrsFromMyst: (token) => ({
+ id: token.identifier || null,
+ label: token.label || null,
+ numbered: false,
+ language: langToLanguage(token.lang) || null,
+ linenumbers: token.showLineNumbers || false,
+ title: '',
+ }),
+ toMyst: (props) => {
+ if (props.children?.length === 1) {
+ return {
+ type: 'code',
+ lang: languageToLang(props.language || undefined),
+ showLineNumbers: props.linenumbers || undefined,
+ value: props.children[0].value || '',
+ };
+ }
+ throw new Error(`Code block node does not have one child`);
+ },
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
const { language } = node.attrs;
@@ -139,5 +140,3 @@ export const toTex = createLatexStatement(
state.renderContent(node);
},
);
-
-export default code_block;
diff --git a/packages/schema/src/nodes/equation.ts b/packages/schema/src/nodes/equation.ts
index 071abb8b..f99760b4 100644
--- a/packages/schema/src/nodes/equation.ts
+++ b/packages/schema/src/nodes/equation.ts
@@ -1,5 +1,4 @@
-import type { MyNodeSpec, NumberedNode } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup, NumberedNode } from './types';
import type { Math } from '../spec';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import {
@@ -18,59 +17,62 @@ export type Attrs = NumberedNode & {
const DEFAULT_NUMBERED = false;
-const equation: MyNodeSpec = {
- group: NodeGroups.top,
- // Content can have display elements inside of it for dynamic equations
- content: `(${NodeGroups.text} | display)*`,
- draggable: false,
- marks: '',
- // The view treat the node as a leaf, even though it technically has content
- atom: true,
- whitespace: 'pre',
- code: true,
- attrs: {
- ...getNumberedDefaultAttrs(),
- title: { default: '' },
- },
- toDOM: (node) => {
- const { title } = node.attrs;
- return ['r-equation', { ...setNumberedAttrs(node.attrs), title: title || undefined }, 0];
- },
- parseDOM: [
- {
- tag: 'r-equation:not([inline])',
- getAttrs(dom) {
+export function createEquationNodeSpec(nodeGroup: NodeGroup) {
+ const equation: MyNodeSpec = {
+ group: nodeGroup.top,
+ // Content can have display elements inside of it for dynamic equations
+ content: `(${nodeGroup.text} | display)*`,
+ draggable: false,
+ marks: '',
+ // The view treat the node as a leaf, even though it technically has content
+ atom: true,
+ whitespace: 'pre',
+ code: true,
+ attrs: {
+ ...getNumberedDefaultAttrs(),
+ title: { default: '' },
+ },
+ toDOM: (node) => {
+ const { title } = node.attrs;
+ return ['r-equation', { ...setNumberedAttrs(node.attrs), title: title || undefined }, 0];
+ },
+ parseDOM: [
+ {
+ tag: 'r-equation:not([inline])',
+ getAttrs(dom) {
+ return {
+ ...getNumberedAttrs(dom),
+ title: getAttr(dom, 'title'),
+ };
+ },
+ },
+ ],
+ attrsFromMyst: (token) => ({
+ id: token.identifier || null,
+ label: null,
+ numbered: token.enumerated ?? DEFAULT_NUMBERED,
+ title: '',
+ }),
+ toMyst: (props, options) => {
+ if (props.children?.length === 1 && props.children[0].type === 'text') {
+ const localizedId = options.localizeId?.(props.id ?? '') ?? props.id ?? '';
return {
- ...getNumberedAttrs(dom),
- title: getAttr(dom, 'title'),
+ type: 'math',
+ ...normalizeLabel(localizedId),
+ enumerated: readBooleanAttr(props.numbered),
+ value: props.children[0].value || '',
};
- },
+ }
+ throw new Error(`Equation node does not have one child`);
},
- ],
- attrsFromMyst: (token) => ({
- id: token.identifier || null,
- label: null,
- numbered: token.enumerated ?? DEFAULT_NUMBERED,
- title: '',
- }),
- toMyst: (props, options) => {
- if (props.children?.length === 1 && props.children[0].type === 'text') {
- const localizedId = options.localizeId?.(props.id ?? '') ?? props.id ?? '';
- return {
- type: 'math',
- ...normalizeLabel(localizedId),
- enumerated: readBooleanAttr(props.numbered),
- value: props.children[0].value || '',
- };
- }
- throw new Error(`Equation node does not have one child`);
- },
-};
+ };
+ const equationNoDisplay: MyNodeSpec = {
+ ...equation,
+ content: `${nodeGroup.text}*`,
+ };
-export const equationNoDisplay: MyNodeSpec = {
- ...equation,
- content: `${NodeGroups.text}*`,
-};
+ return { equation, equationNoDisplay };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
const { numbered, id } = node.attrs;
@@ -128,5 +130,3 @@ export const toTex: TexFormatSerialize = (state, node) => {
}
if (!state.isInTable) state.closeBlock(node);
};
-
-export default equation;
diff --git a/packages/schema/src/nodes/figcaption.ts b/packages/schema/src/nodes/figcaption.ts
index 5f7d0ca2..07dc0634 100644
--- a/packages/schema/src/nodes/figcaption.ts
+++ b/packages/schema/src/nodes/figcaption.ts
@@ -3,7 +3,7 @@ import type { Caption, FlowContent } from '../spec';
import type { MdFormatSerialize } from '../serialize/types';
import { createLatexStatement } from '../serialize/tex/utils';
import type { MyNodeSpec } from './types';
-import { NodeGroups, CaptionKind } from './types';
+import { LEGACY_NODE_GROUPS, CaptionKind } from './types';
import { nodeNames } from '../types';
export type Attrs = {
@@ -11,10 +11,11 @@ export type Attrs = {
};
const figcaption: MyNodeSpec = {
- content: `${NodeGroups.inline}*`,
+ content: `${LEGACY_NODE_GROUPS.inline}*`,
attrs: {
kind: { default: null },
},
+ isolating: true,
draggable: false,
defining: true,
toDOM(node) {
diff --git a/packages/schema/src/nodes/figure.ts b/packages/schema/src/nodes/figure.ts
index 295205dc..7207d2de 100644
--- a/packages/schema/src/nodes/figure.ts
+++ b/packages/schema/src/nodes/figure.ts
@@ -2,8 +2,8 @@ import type { Node } from 'prosemirror-model';
import type { Caption, Container, Image, Legend, Table } from '../spec';
import type { MdFormatSerialize } from '../serialize/types';
import { createLatexStatement } from '../serialize/tex/utils';
-import type { AlignOptions, MyNodeSpec, NumberedNode } from './types';
-import { CaptionKind, NodeGroups } from './types';
+import type { AlignOptions, MyNodeSpec, NodeGroup, NumberedNode } from './types';
+import { CaptionKind } from './types';
import { determineCaptionKind } from '../process/utils';
import {
getColumnWidths,
@@ -30,78 +30,81 @@ export type Attrs = NumberedNode & {
fullpage: boolean;
};
-const figure: MyNodeSpec = {
- group: NodeGroups.block,
- content: NodeGroups.insideFigure,
- isolating: true,
- attrs: {
- ...getNumberedDefaultAttrs(),
- align: { default: 'center' },
- multipage: { default: false },
- landscape: { default: false },
- fullpage: { default: false },
- },
- toDOM(node) {
- const { align, multipage, landscape, fullpage } = node.attrs;
- return [
- 'figure',
+export function createFigureNodeSpec(nodeGroup: NodeGroup) {
+ const figure: MyNodeSpec = {
+ group: nodeGroup.content,
+ content: nodeGroup.insideFigure,
+ isolating: true,
+ attrs: {
+ ...getNumberedDefaultAttrs(),
+ align: { default: 'center' },
+ multipage: { default: false },
+ landscape: { default: false },
+ fullpage: { default: false },
+ },
+ toDOM(node) {
+ const { align, multipage, landscape, fullpage } = node.attrs;
+ return [
+ 'figure',
+ {
+ ...setNumberedAttrs(node.attrs),
+ align,
+ multipage,
+ landscape,
+ fullpage,
+ },
+ 0,
+ ];
+ },
+ parseDOM: [
{
- ...setNumberedAttrs(node.attrs),
- align,
- multipage,
- landscape,
- fullpage,
- },
- 0,
- ];
- },
- parseDOM: [
- {
- tag: 'figure',
- getAttrs(dom) {
- return {
- ...getNumberedAttrs(dom),
- align: dom.getAttribute('align') ?? 'center',
- multipage: readBooleanDomAttr(dom, 'multipage'),
- landscape: readBooleanDomAttr(dom, 'landscape'),
- fullpage: readBooleanDomAttr(dom, 'fullpage'),
- };
+ tag: 'figure',
+ getAttrs(dom) {
+ return {
+ ...getNumberedAttrs(dom),
+ align: dom.getAttribute('align') ?? 'center',
+ multipage: readBooleanDomAttr(dom, 'multipage'),
+ landscape: readBooleanDomAttr(dom, 'landscape'),
+ fullpage: readBooleanDomAttr(dom, 'fullpage'),
+ };
+ },
},
+ ],
+ attrsFromMyst: (token) => {
+ const match = token.class?.match(/align-(left|right|center)/);
+ return {
+ id: token.identifier || null,
+ label: null, // This is deprecated
+ numbered: token.enumerated || false,
+ align: (match ? match[1] : 'center') as AlignOptions,
+ multipage: false,
+ landscape: false,
+ fullpage: false,
+ };
},
- ],
- attrsFromMyst: (token) => {
- const match = token.class?.match(/align-(left|right|center)/);
- return {
- id: token.identifier || null,
- label: null, // This is deprecated
- numbered: token.enumerated || false,
- align: (match ? match[1] : 'center') as AlignOptions,
- multipage: false,
- landscape: false,
- fullpage: false,
- };
- },
- toMyst: (props, options) => {
- let containerKind: 'figure' | 'table' = 'figure';
- props.children?.forEach((child) => {
- if (child.type === 'image' || child.type === 'table') {
- child.align = props.align || undefined;
- }
- if (child.type === 'table') {
- containerKind = 'table';
- }
- });
- const localizedId = options.localizeId?.(props.id ?? '') ?? props.id ?? '';
- return {
- type: 'container',
- kind: containerKind,
- ...normalizeLabel(localizedId),
- enumerated: readBooleanAttr(props.numbered),
- class: props.align ? `align-${props.align}` : undefined,
- children: (props.children || []) as (Caption | Legend | Image | Table)[],
- };
- },
-};
+ toMyst: (props, options) => {
+ let containerKind: 'figure' | 'table' = 'figure';
+ props.children?.forEach((child) => {
+ if (child.type === 'image' || child.type === 'table') {
+ child.align = props.align || undefined;
+ }
+ if (child.type === 'table') {
+ containerKind = 'table';
+ }
+ });
+ const localizedId = options.localizeId?.(props.id ?? '') ?? props.id ?? '';
+ return {
+ type: 'container',
+ kind: containerKind,
+ ...normalizeLabel(localizedId),
+ enumerated: readBooleanAttr(props.numbered),
+ class: props.align ? `align-${props.align}` : undefined,
+ children: (props.children || []) as (Caption | Legend | Image | Table)[],
+ };
+ },
+ };
+ return figure;
+}
export const toMarkdown: MdFormatSerialize = (state, node, parent, index) => {
state.ensureNewLine();
@@ -254,5 +257,3 @@ export const toTex = createLatexStatement(
state.containsTable = false;
},
);
-
-export default figure;
diff --git a/packages/schema/src/nodes/footnote.ts b/packages/schema/src/nodes/footnote.ts
index c36badf4..c904f7dd 100644
--- a/packages/schema/src/nodes/footnote.ts
+++ b/packages/schema/src/nodes/footnote.ts
@@ -1,12 +1,13 @@
import type { FlowContent, InlineFootnote, NoAttrs } from '../spec';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import type { MyNodeSpec, Props } from './types';
-import { NodeGroups } from './types';
+import { LEGACY_NODE_GROUPS } from './types';
+import { nodeNames } from '../types';
const footnote: MyNodeSpec = {
attrs: {},
- group: 'inline',
- content: `(${NodeGroups.text} | math)*`,
+ group: LEGACY_NODE_GROUPS.inline,
+ content: `(${LEGACY_NODE_GROUPS.text} | ${nodeNames.math})*`,
inline: true,
draggable: true,
// This makes the view treat the node as a leaf, even though it
diff --git a/packages/schema/src/nodes/heading.ts b/packages/schema/src/nodes/heading.ts
index 84316c2b..5b8fc5cf 100644
--- a/packages/schema/src/nodes/heading.ts
+++ b/packages/schema/src/nodes/heading.ts
@@ -1,7 +1,6 @@
import type { Heading, PhrasingContent } from '../spec';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
-import type { MyNodeSpec, NumberedNode } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup, NumberedNode } from './types';
import {
getNumberedAttrs,
getNumberedDefaultAttrs,
@@ -18,38 +17,40 @@ export type Attrs = NumberedNode & {
level: number;
};
-const heading: MyNodeSpec = {
- attrs: {
- ...getNumberedDefaultAttrs(),
- level: { default: 1 },
- },
- content: `${NodeGroups.inline}*`,
- group: NodeGroups.heading,
- defining: true,
- parseDOM: [
- { tag: 'h1', getAttrs: getAttrs(1) },
- { tag: 'h2', getAttrs: getAttrs(2) },
- { tag: 'h3', getAttrs: getAttrs(3) },
- { tag: 'h4', getAttrs: getAttrs(4) },
- { tag: 'h5', getAttrs: getAttrs(5) },
- { tag: 'h6', getAttrs: getAttrs(6) },
- ],
- toDOM(node) {
- return [`h${node.attrs.level}`, setNumberedAttrs(node.attrs), 0];
- },
- attrsFromMyst: (token) => ({
- id: null,
- label: null,
- numbered: token.enumerated ?? false,
- level: token.depth,
- }),
- toMyst: (props) => ({
- type: 'heading',
- depth: parseInt(props.tag.slice(1), 10),
- enumerated: readBooleanAttr(props.numbered),
- children: (props.children || []) as PhrasingContent[],
- }),
-};
+export function createHeadingNodeSpec(nodeGroup: NodeGroup): MyNodeSpec {
+ return {
+ attrs: {
+ ...getNumberedDefaultAttrs(),
+ level: { default: 1 },
+ },
+ content: `${nodeGroup.inline}*`,
+ group: nodeGroup.heading,
+ defining: true,
+ parseDOM: [
+ { tag: 'h1', getAttrs: getAttrs(1) },
+ { tag: 'h2', getAttrs: getAttrs(2) },
+ { tag: 'h3', getAttrs: getAttrs(3) },
+ { tag: 'h4', getAttrs: getAttrs(4) },
+ { tag: 'h5', getAttrs: getAttrs(5) },
+ { tag: 'h6', getAttrs: getAttrs(6) },
+ ],
+ toDOM(node) {
+ return [`h${node.attrs.level}`, setNumberedAttrs(node.attrs), 0];
+ },
+ attrsFromMyst: (token) => ({
+ id: null,
+ label: null,
+ numbered: token.enumerated ?? false,
+ level: token.depth,
+ }),
+ toMyst: (props) => ({
+ type: 'heading',
+ depth: parseInt(props.tag.slice(1), 10),
+ enumerated: readBooleanAttr(props.numbered),
+ children: (props.children || []) as PhrasingContent[],
+ }),
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
// TODO: Put the id in:
@@ -73,5 +74,3 @@ export const toTex: TexFormatSerialize = (state, node) => {
}
state.closeBlock(node);
};
-
-export default heading;
diff --git a/packages/schema/src/nodes/iframe.ts b/packages/schema/src/nodes/iframe.ts
index a1ed62fc..79ad63f9 100644
--- a/packages/schema/src/nodes/iframe.ts
+++ b/packages/schema/src/nodes/iframe.ts
@@ -2,8 +2,7 @@ import { DEFAULT_IMAGE_WIDTH } from '../defaults';
import { writeDirectiveOptions } from '../serialize/markdown/utils';
import type { MdFormatSerialize } from '../serialize/types';
import type { Iframe } from '../spec';
-import type { MyNodeSpec, AlignOptions } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, AlignOptions, NodeGroup } from './types';
import { getImageWidth } from './utils';
export type Attrs = {
@@ -12,48 +11,50 @@ export type Attrs = {
width: number | null;
};
-const iframe: MyNodeSpec = {
- attrs: {
- src: {},
- align: { default: 'center' },
- width: { default: DEFAULT_IMAGE_WIDTH },
- },
- group: NodeGroups.block,
- draggable: true,
- parseDOM: [
- {
- tag: 'iframe[src]',
- getAttrs(dom: any) {
- return {
- src: dom.getAttribute('src'),
- align: dom.getAttribute('align') ?? 'center',
- width: getImageWidth(dom.getAttribute('width')),
- };
- },
+export function createIframeSpec(nodeGroup: NodeGroup): MyNodeSpec {
+ return {
+ attrs: {
+ src: {},
+ align: { default: 'center' },
+ width: { default: DEFAULT_IMAGE_WIDTH },
},
- ],
- toDOM(node) {
- const { src, align, width } = node.attrs;
- return [
- 'iframe',
+ group: nodeGroup.content,
+ draggable: true,
+ parseDOM: [
{
- src,
- align,
- width: `${width}%`,
+ tag: 'iframe[src]',
+ getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute('src'),
+ align: dom.getAttribute('align') ?? 'center',
+ width: getImageWidth(dom.getAttribute('width')),
+ };
+ },
},
- ];
- },
- attrsFromMyst: (node) => ({
- src: node.src,
- align: 'center',
- width: getImageWidth(node.width),
- }),
- toMyst: (node) => ({
- type: 'iframe',
- src: node.src,
- width: node.width,
- }),
-};
+ ],
+ toDOM(node) {
+ const { src, align, width } = node.attrs;
+ return [
+ 'iframe',
+ {
+ src,
+ align,
+ width: `${width}%`,
+ },
+ ];
+ },
+ attrsFromMyst: (node) => ({
+ src: node.src,
+ align: 'center',
+ width: getImageWidth(node.width),
+ }),
+ toMyst: (node) => ({
+ type: 'iframe',
+ src: node.src,
+ width: node.width,
+ }),
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
const renderer = state.options.renderers?.iframe ?? 'html';
@@ -89,5 +90,3 @@ export const toMarkdown: MdFormatSerialize = (state, node) => {
state.write('```');
state.closeBlock(node);
};
-
-export default iframe;
diff --git a/packages/schema/src/nodes/image.ts b/packages/schema/src/nodes/image.ts
index cb23fa6d..dad0deee 100644
--- a/packages/schema/src/nodes/image.ts
+++ b/packages/schema/src/nodes/image.ts
@@ -1,7 +1,6 @@
import type { Image } from '../spec';
import { DEFAULT_IMAGE_WIDTH } from '../defaults';
-import type { NumberedNode, MyNodeSpec, AlignOptions } from './types';
-import { NodeGroups } from './types';
+import type { NumberedNode, MyNodeSpec, AlignOptions, NodeGroup } from './types';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import {
getImageWidth,
@@ -20,68 +19,70 @@ export type Attrs = NumberedNode & {
caption: boolean;
};
-const image: MyNodeSpec = {
- attrs: {
- ...getNumberedDefaultAttrs(), // Deprecated, use figure
- src: {},
- alt: { default: null },
- title: { default: null },
- width: { default: DEFAULT_IMAGE_WIDTH },
- align: { default: 'center' }, // Deprecated, use figure
- caption: { default: false }, // Deprecated, use figcaption
- },
- group: NodeGroups.block,
- draggable: true,
- parseDOM: [
- {
- tag: 'img[src]',
- getAttrs(dom) {
- return {
- ...getNumberedAttrs(dom), // Deprecated, use figure
- src: dom.getAttribute('src'),
- title: dom.getAttribute('title'),
- alt: dom.getAttribute('alt'),
- align: dom.getAttribute('align') ?? 'center', // Deprecated, use figure
- width: getImageWidth(dom.getAttribute('width')),
- caption: readBooleanDomAttr(dom, 'caption'),
- };
- },
+export function createImageNodeSpec(nodeGroup: NodeGroup): MyNodeSpec {
+ return {
+ attrs: {
+ ...getNumberedDefaultAttrs(), // Deprecated, use figure
+ src: {},
+ alt: { default: null },
+ title: { default: null },
+ width: { default: DEFAULT_IMAGE_WIDTH },
+ align: { default: 'center' }, // Deprecated, use figure
+ caption: { default: false }, // Deprecated, use figcaption
},
- ],
- toDOM(node) {
- const { src, alt, title, width, align } = node.attrs;
- return [
- 'img',
+ group: nodeGroup.content,
+ draggable: true,
+ parseDOM: [
{
- ...setNumberedAttrs(node.attrs), // Deprecated, use figure
- src,
- align,
- alt: alt || undefined,
- title: title || undefined,
- width: `${width}%`,
+ tag: 'img[src]',
+ getAttrs(dom) {
+ return {
+ ...getNumberedAttrs(dom), // Deprecated, use figure
+ src: dom.getAttribute('src'),
+ title: dom.getAttribute('title'),
+ alt: dom.getAttribute('alt'),
+ align: dom.getAttribute('align') ?? 'center', // Deprecated, use figure
+ width: getImageWidth(dom.getAttribute('width')),
+ caption: readBooleanDomAttr(dom, 'caption'),
+ };
+ },
},
- ];
- },
- attrsFromMyst: (token) => ({
- id: null, // Deprecated, use figure / container
- label: null, // Deprecated, use figure / container
- numbered: false, // Deprecated, use figure / container
- src: token.url || '',
- alt: token.alt || '',
- title: token.title || '',
- width: getImageWidth(token.width),
- align: token.align || 'center',
- caption: false,
- }),
- toMyst: (props, opts): Image => ({
- type: 'image',
- url: opts.localizeImageSrc?.(props.src) || props.src,
- alt: props.alt || undefined,
- title: props.title || undefined,
- align: undefined,
- width: `${getImageWidth(props.width)}%` || undefined,
- }),
-};
+ ],
+ toDOM(node) {
+ const { src, alt, title, width, align } = node.attrs;
+ return [
+ 'img',
+ {
+ ...setNumberedAttrs(node.attrs), // Deprecated, use figure
+ src,
+ align,
+ alt: alt || undefined,
+ title: title || undefined,
+ width: `${width}%`,
+ },
+ ];
+ },
+ attrsFromMyst: (token) => ({
+ id: null, // Deprecated, use figure / container
+ label: null, // Deprecated, use figure / container
+ numbered: false, // Deprecated, use figure / container
+ src: token.url || '',
+ alt: token.alt || '',
+ title: token.title || '',
+ width: getImageWidth(token.width),
+ align: token.align || 'center',
+ caption: false,
+ }),
+ toMyst: (props, opts): Image => ({
+ type: 'image',
+ url: opts.localizeImageSrc?.(props.src) || props.src,
+ alt: props.alt || undefined,
+ title: props.title || undefined,
+ align: undefined,
+ width: `${getImageWidth(props.width)}%` || undefined,
+ }),
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
const src = state.options.localizeImageSrc?.(node.attrs.src) || node.attrs.src;
@@ -118,5 +119,3 @@ export const toTex: TexFormatSerialize = (state, node) => {
state.write(`\\includegraphics[width=${width / 100}\\linewidth]{${src}}`);
state.closeBlock(node);
};
-
-export default image;
diff --git a/packages/schema/src/nodes/linkBlock.ts b/packages/schema/src/nodes/linkBlock.ts
index fc8af41e..a19a4224 100644
--- a/packages/schema/src/nodes/linkBlock.ts
+++ b/packages/schema/src/nodes/linkBlock.ts
@@ -1,5 +1,4 @@
-import type { MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup } from './types';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import type { StaticPhrasingContent, LinkBlock } from '../spec';
@@ -11,63 +10,65 @@ export interface Attrs {
const LINK_BLOCK_CLASS = 'link-block';
-const link_block: MyNodeSpec = {
- attrs: {
- url: { default: '' },
- title: { default: '' },
- description: { default: '' },
- },
- group: NodeGroups.top,
- content: `${NodeGroups.text}*`,
- selectable: true,
- draggable: true,
- atom: true,
- isolating: true,
- parseDOM: [
- {
- tag: `div.${LINK_BLOCK_CLASS}`,
- getAttrs(dom: any) {
- const attrs = {
- url: dom.getAttribute('data-url') || null,
- title: dom.getAttribute('title') || '',
- description: dom.textContent || '',
- };
- return attrs;
- },
+export function createLinkBlockNodeSpec(nodeGroup: NodeGroup): MyNodeSpec {
+ return {
+ attrs: {
+ url: { default: '' },
+ title: { default: '' },
+ description: { default: '' },
},
- ],
- toDOM(node: any) {
- const { title, description, url } = node.attrs;
- return [
- 'div',
+ group: nodeGroup.top,
+ content: `${nodeGroup.text}*`,
+ selectable: true,
+ draggable: true,
+ atom: true,
+ isolating: true,
+ parseDOM: [
{
- 'data-url': url || undefined,
- title,
- class: LINK_BLOCK_CLASS,
+ tag: `div.${LINK_BLOCK_CLASS}`,
+ getAttrs(dom: any) {
+ const attrs = {
+ url: dom.getAttribute('data-url') || null,
+ title: dom.getAttribute('title') || '',
+ description: dom.textContent || '',
+ };
+ return attrs;
+ },
},
- description,
- ];
- },
- attrsFromMyst: (token) => {
- let description = '';
- if (token.children.length && token.children[0].type === 'text') {
- description = token.children[0].value;
- }
- return {
- url: token.url,
- title: token.title || '',
- description,
- };
- },
- toMyst: (props) => {
- return {
- type: 'linkBlock',
- url: props['data-url'],
- title: props.title || undefined,
- children: (props.children || []) as StaticPhrasingContent[],
- };
- },
-};
+ ],
+ toDOM(node: any) {
+ const { title, description, url } = node.attrs;
+ return [
+ 'div',
+ {
+ 'data-url': url || undefined,
+ title,
+ class: LINK_BLOCK_CLASS,
+ },
+ description,
+ ];
+ },
+ attrsFromMyst: (token) => {
+ let description = '';
+ if (token.children.length && token.children[0].type === 'text') {
+ description = token.children[0].value;
+ }
+ return {
+ url: token.url,
+ title: token.title || '',
+ description,
+ };
+ },
+ toMyst: (props) => {
+ return {
+ type: 'linkBlock',
+ url: props['data-url'],
+ title: props.title || undefined,
+ children: (props.children || []) as StaticPhrasingContent[],
+ };
+ },
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
state.ensureNewLine();
@@ -87,5 +88,3 @@ export const toTex: TexFormatSerialize = (state, node) => {
state.write(`\\url{${node.attrs.url}}`);
}
};
-
-export default link_block;
diff --git a/packages/schema/src/nodes/math.ts b/packages/schema/src/nodes/math.ts
index af6015f2..688bdc03 100644
--- a/packages/schema/src/nodes/math.ts
+++ b/packages/schema/src/nodes/math.ts
@@ -1,39 +1,42 @@
import type { InlineMath } from '../spec';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
-import type { MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import type { MyNodeSpec, NodeGroup } from './types';
export type Attrs = Record;
-const math: MyNodeSpec = {
- group: NodeGroups.inline,
- // Content can have display elements inside of it for dynamic equations
- content: `(${NodeGroups.text} | display)*`,
- inline: true,
- marks: '',
- draggable: false,
- // The view treat the node as a leaf, even though it technically has content
- atom: true,
- attrs: {},
- toDOM: () => ['r-equation', { inline: '' }, 0],
- parseDOM: [
- {
- tag: 'r-equation[inline]',
+export function createMathNodeSpec(nodeGroup: NodeGroup) {
+ const math: MyNodeSpec = {
+ group: nodeGroup.inline,
+ // Content can have display elements inside of it for dynamic equations
+ content: `(${nodeGroup.text} | display)*`,
+ inline: true,
+ marks: '',
+ draggable: false,
+ // The view treat the node as a leaf, even though it technically has content
+ atom: true,
+ attrs: {},
+ toDOM: () => ['r-equation', { inline: '' }, 0],
+ parseDOM: [
+ {
+ tag: 'r-equation[inline]',
+ },
+ ],
+ attrsFromMyst: () => ({}),
+ toMyst: (props) => {
+ if (props.children?.length === 1 && props.children[0].type === 'text') {
+ return { type: 'inlineMath', value: props.children[0].value || '' };
+ }
+ throw new Error(`Math node does not have one child`);
},
- ],
- attrsFromMyst: () => ({}),
- toMyst: (props) => {
- if (props.children?.length === 1 && props.children[0].type === 'text') {
- return { type: 'inlineMath', value: props.children[0].value || '' };
- }
- throw new Error(`Math node does not have one child`);
- },
-};
-
-export const mathNoDisplay: MyNodeSpec = {
- ...math,
- content: `${NodeGroups.text}*`,
-};
+ };
+ return {
+ math,
+ mathNoDisplay: {
+ ...math,
+ content: `${nodeGroup.text}*`,
+ } as MyNodeSpec,
+ };
+}
export const toMarkdown: MdFormatSerialize = (state, node) => {
state.write('$');
@@ -47,5 +50,3 @@ export const toTex: TexFormatSerialize = (state, node) => {
state.renderInline(node);
state.write('$');
};
-
-export default math;
diff --git a/packages/schema/src/nodes/mention.ts b/packages/schema/src/nodes/mention.ts
index 59de0404..7c0186de 100644
--- a/packages/schema/src/nodes/mention.ts
+++ b/packages/schema/src/nodes/mention.ts
@@ -1,5 +1,5 @@
import type { MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import { LEGACY_NODE_GROUPS } from './types';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import type { Mention } from '../spec';
@@ -9,7 +9,7 @@ export interface Attrs {
}
const mention: MyNodeSpec = {
- group: NodeGroups.inline,
+ group: LEGACY_NODE_GROUPS.inline,
attrs: { label: { default: '' }, user: { default: '' } },
inline: true,
draggable: true,
diff --git a/packages/schema/src/nodes/reactive/button.ts b/packages/schema/src/nodes/reactive/button.ts
index daaeb999..8a8d90cc 100644
--- a/packages/schema/src/nodes/reactive/button.ts
+++ b/packages/schema/src/nodes/reactive/button.ts
@@ -1,8 +1,8 @@
-import type { NodeDef } from '../types';
-import { NodeGroups } from '../types';
+import type { NodeGroup } from '../types';
+import { LEGACY_NODE_GROUPS } from '../types';
import type { MdFormatSerialize } from '../../serialize/types';
import { createAttr as attr, nodeToMystRole, createSpec } from './utils';
-import type { Button } from '../../spec';
+import { buildDef } from '../utils';
export type Attrs = {
label?: string;
@@ -12,15 +12,16 @@ export type Attrs = {
clickFunction?: string;
};
-export const def: NodeDef = {
+const def = {
tag: 'r-button',
name: 'r:button',
mystType: 'reactiveButton',
attrs: [attr('label', true, 'Click Here'), attr('click', 'only'), attr('disabled')],
inline: true,
- group: NodeGroups.inline,
};
+export function createNodeSpec(nodeGroup: NodeGroup) {
+ return createSpec(buildDef(nodeGroup, def));
+}
-export const spec = createSpec(def);
-export const toMarkdown: MdFormatSerialize = (state, node) => nodeToMystRole(state, node, def);
-export default spec;
+export const toMarkdown: MdFormatSerialize = (state, node) =>
+ nodeToMystRole(state, node, buildDef(LEGACY_NODE_GROUPS, def));
diff --git a/packages/schema/src/nodes/reactive/display.ts b/packages/schema/src/nodes/reactive/display.ts
index 20de80c5..2b832998 100644
--- a/packages/schema/src/nodes/reactive/display.ts
+++ b/packages/schema/src/nodes/reactive/display.ts
@@ -1,8 +1,9 @@
-import type { NodeDef } from '../types';
-import { NodeGroups } from '../types';
+import type { NodeGroup } from '../types';
+import { LEGACY_NODE_GROUPS } from '../types';
import type { MdFormatSerialize } from '../../serialize/types';
import { createAttr as attr, nodeToMystRole, createSpec } from './utils';
import type { Display } from '../../spec';
+import { buildDef } from '../utils';
export type Attrs = {
value?: string;
@@ -11,15 +12,16 @@ export type Attrs = {
transformFunction?: string;
};
-export const def: NodeDef = {
+const def = {
tag: 'r-display',
name: 'r:display',
mystType: 'reactiveDisplay',
attrs: [attr('value'), attr('format', false), attr('transform', 'only', '')],
inline: true,
- group: NodeGroups.inline,
};
+export function createNodeSpec(nodeGroup: NodeGroup) {
+ return createSpec(buildDef(nodeGroup, def));
+}
-export const spec = createSpec(def);
-export const toMarkdown: MdFormatSerialize = (state, node) => nodeToMystRole(state, node, def);
-export default spec;
+export const toMarkdown: MdFormatSerialize = (state, node) =>
+ nodeToMystRole(state, node, buildDef(LEGACY_NODE_GROUPS, def));
diff --git a/packages/schema/src/nodes/reactive/dynamic.ts b/packages/schema/src/nodes/reactive/dynamic.ts
index c49c623d..187cd764 100644
--- a/packages/schema/src/nodes/reactive/dynamic.ts
+++ b/packages/schema/src/nodes/reactive/dynamic.ts
@@ -1,8 +1,8 @@
-import type { NodeDef } from '../types';
-import { NodeGroups } from '../types';
+import type { NodeGroup } from '../types';
+import { LEGACY_NODE_GROUPS } from '../types';
import type { MdFormatSerialize } from '../../serialize/types';
import { createAttr as attr, nodeToMystRole, createSpec } from './utils';
-import type { Dynamic } from '../../spec';
+import { buildDef } from '../utils';
export type Attrs = {
value?: string;
@@ -17,7 +17,7 @@ export type Attrs = {
stepFunction?: string;
};
-export const def: NodeDef = {
+const ref = {
tag: 'r-dynamic',
name: 'r:dynamic',
mystType: 'reactiveDynamic',
@@ -30,9 +30,11 @@ export const def: NodeDef = {
attr('step', true, '1'),
],
inline: true,
- group: NodeGroups.inline,
};
-export const spec = createSpec(def);
-export const toMarkdown: MdFormatSerialize = (state, node) => nodeToMystRole(state, node, def);
-export default spec;
+export function createNodeSpec(nodeGroup: NodeGroup) {
+ return createSpec(buildDef(nodeGroup, ref));
+}
+
+export const toMarkdown: MdFormatSerialize = (state, node) =>
+ nodeToMystRole(state, node, buildDef(LEGACY_NODE_GROUPS, ref));
diff --git a/packages/schema/src/nodes/reactive/range.ts b/packages/schema/src/nodes/reactive/range.ts
index 84ec8747..7af3943b 100644
--- a/packages/schema/src/nodes/reactive/range.ts
+++ b/packages/schema/src/nodes/reactive/range.ts
@@ -1,8 +1,8 @@
-import type { NodeDef } from '../types';
-import { NodeGroups } from '../types';
+import type { NodeGroup } from '../types';
+import { LEGACY_NODE_GROUPS } from '../types';
import type { MdFormatSerialize } from '../../serialize/types';
import { createAttr as attr, nodeToMystRole, createSpec } from './utils';
-import type { Range } from '../../spec';
+import { buildDef } from '../utils';
export type Attrs = {
value?: string;
@@ -17,7 +17,7 @@ export type Attrs = {
stepFunction?: string;
};
-export const def: NodeDef = {
+const def = {
tag: 'r-range',
name: 'r:range',
mystType: 'reactiveRange',
@@ -30,9 +30,10 @@ export const def: NodeDef = {
attr('step', true, '1'),
],
inline: true,
- group: NodeGroups.inline,
};
+export function createNodeSpec(nodeGroup: NodeGroup) {
+ return createSpec(buildDef(nodeGroup, def));
+}
-export const spec = createSpec(def);
-export const toMarkdown: MdFormatSerialize = (state, node) => nodeToMystRole(state, node, def);
-export default spec;
+export const toMarkdown: MdFormatSerialize = (state, node) =>
+ nodeToMystRole(state, node, buildDef(LEGACY_NODE_GROUPS, def));
diff --git a/packages/schema/src/nodes/reactive/switch.ts b/packages/schema/src/nodes/reactive/switch.ts
index 8892a7ac..52e4adfc 100644
--- a/packages/schema/src/nodes/reactive/switch.ts
+++ b/packages/schema/src/nodes/reactive/switch.ts
@@ -1,8 +1,8 @@
-import type { NodeDef } from '../types';
-import { NodeGroups } from '../types';
+import type { NodeGroup } from '../types';
+import { LEGACY_NODE_GROUPS } from '../types';
import type { MdFormatSerialize } from '../../serialize/types';
import { createAttr as attr, nodeToMystRole, createSpec } from './utils';
-import type { Switch } from '../../spec';
+import { buildDef } from '../utils';
export type Attrs = {
value?: string;
@@ -11,15 +11,16 @@ export type Attrs = {
label?: string;
};
-export const def: NodeDef = {
+const def = {
tag: 'r-switch',
name: 'r:switch',
mystType: 'reactiveSwitch',
attrs: [attr('value'), attr('change', 'only'), attr('label', false)],
inline: true,
- group: NodeGroups.inline,
};
+export function createNodeSpec(nodeGroup: NodeGroup) {
+ return createSpec(buildDef(nodeGroup, def));
+}
-export const spec = createSpec(def);
-export const toMarkdown: MdFormatSerialize = (state, node) => nodeToMystRole(state, node, def);
-export default spec;
+export const toMarkdown: MdFormatSerialize = (state, node) =>
+ nodeToMystRole(state, node, buildDef(LEGACY_NODE_GROUPS, def));
diff --git a/packages/schema/src/nodes/reactive/variable.ts b/packages/schema/src/nodes/reactive/variable.ts
index cb842870..fe493367 100644
--- a/packages/schema/src/nodes/reactive/variable.ts
+++ b/packages/schema/src/nodes/reactive/variable.ts
@@ -1,8 +1,8 @@
-import type { NodeDef } from '../types';
-import { NodeGroups } from '../types';
+import type { NodeGroup } from '../types';
+import { LEGACY_NODE_GROUPS } from '../types';
import type { MdFormatSerialize } from '../../serialize/types';
import { createAttr as attr, createSpec, DEFAULT_FORMAT, nodeToMystDirective } from './utils';
-import type { Variable } from '../../spec';
+import { buildDef } from '../utils';
export type Attrs = {
name: string;
@@ -11,15 +11,16 @@ export type Attrs = {
format?: string;
};
-export const def: NodeDef = {
+export const def = {
tag: 'r-var',
name: 'r:var',
mystType: 'reactiveVariable',
attrs: [attr('name', false, false), attr('value'), attr('format', false, DEFAULT_FORMAT)],
inline: false,
- group: NodeGroups.top,
};
-const spec = createSpec(def);
-export const toMarkdown: MdFormatSerialize = (state, node) => nodeToMystDirective(state, node, def);
-export default spec;
+export function createNodeSpec(nodeGroup: NodeGroup) {
+ return createSpec(buildDef(nodeGroup, def, 'top'));
+}
+export const toMarkdown: MdFormatSerialize = (state, node) =>
+ nodeToMystDirective(state, node, buildDef(LEGACY_NODE_GROUPS, def));
diff --git a/packages/schema/src/nodes/table.ts b/packages/schema/src/nodes/table.ts
index ae5eea87..39af2d2b 100644
--- a/packages/schema/src/nodes/table.ts
+++ b/packages/schema/src/nodes/table.ts
@@ -4,68 +4,69 @@ import { Fragment } from 'prosemirror-model';
import type { PhrasingContent, Table, TableCell, TableRow } from '../spec';
import type { MdFormatSerialize, TexFormatSerialize, TexSerializerState } from '../types';
import { nodeNames } from '../types';
-import type { Props } from './types';
-import { NodeGroups } from './types';
+import type { NodeGroup, Props } from './types';
import { writeDirectiveOptions } from '../serialize/markdown/utils';
import { indent } from '../serialize/indent';
import { writeMdastSnippet, getColumnWidths, hasFancyTable, renderPColumn } from './utils';
-export const nodes = tableNodes({
- tableGroup: NodeGroups.top,
- cellContent: NodeGroups.blockOrEquation,
- cellAttributes: {
- background: {
- default: null,
- getFromDOM(dom: any) {
- return dom.style.backgroundColor || null;
- },
- setDOMAttr(value: any, attrs: any) {
- // eslint-disable-next-line prefer-template
- if (value) attrs.style = (attrs.style || '') + `background-color: ${value};`;
+export function createTableNodeSpec(nodeGroup: NodeGroup) {
+ const nodes = tableNodes({
+ tableGroup: nodeGroup.top,
+ cellContent: nodeGroup.blockOrEquation,
+ cellAttributes: {
+ background: {
+ default: null,
+ getFromDOM(dom: any) {
+ return dom.style.backgroundColor || null;
+ },
+ setDOMAttr(value: any, attrs: any) {
+ // eslint-disable-next-line prefer-template
+ if (value) attrs.style = (attrs.style || '') + `background-color: ${value};`;
+ },
},
},
- },
-});
+ });
+ nodes.table.attrsFromMyst = () => ({});
+ nodes.table.toMyst = (props: Props): Table => {
+ if (props.children?.length === 1 && props.children[0].type === 'table') {
+ return props.children[0] as Table;
+ }
+ return { type: 'table', align: undefined, children: (props.children || []) as TableRow[] };
+ };
+
+ nodes.table_row.attrsFromMyst = () => ({});
+ nodes.table_row.toMyst = (props: Props): TableRow => ({
+ type: 'tableRow',
+ children: (props.children || []) as TableCell[],
+ });
-nodes.table.attrsFromMyst = () => ({});
-nodes.table.toMyst = (props: Props): Table => {
- if (props.children?.length === 1 && props.children[0].type === 'table') {
- return props.children[0] as Table;
+ function ifGreaterThanOne(num?: number): undefined | number {
+ if (!num) return undefined;
+ return num === 1 ? undefined : num;
}
- return { type: 'table', align: undefined, children: (props.children || []) as TableRow[] };
-};
-nodes.table_row.attrsFromMyst = () => ({});
-nodes.table_row.toMyst = (props: Props): TableRow => ({
- type: 'tableRow',
- children: (props.children || []) as TableCell[],
-});
+ nodes.table_header.attrsFromMyst = () => ({});
+ nodes.table_header.toMyst = (props: Props): TableCell => ({
+ type: 'tableCell',
+ header: true,
+ align: props.align || undefined,
+ colspan: ifGreaterThanOne(props.colspan),
+ rowspan: ifGreaterThanOne(props.rowspan),
+ children: (props.children || []) as PhrasingContent[],
+ });
-function ifGreaterThanOne(num?: number): undefined | number {
- if (!num) return undefined;
- return num === 1 ? undefined : num;
+ nodes.table_cell.attrsFromMyst = () => ({});
+ nodes.table_cell.toMyst = (props: Props): TableCell => ({
+ type: 'tableCell',
+ header: undefined,
+ align: props.align || undefined,
+ colspan: ifGreaterThanOne(props.colspan),
+ rowspan: ifGreaterThanOne(props.rowspan),
+ children: (props.children || []) as PhrasingContent[],
+ });
+ return nodes;
}
-nodes.table_header.attrsFromMyst = () => ({});
-nodes.table_header.toMyst = (props: Props): TableCell => ({
- type: 'tableCell',
- header: true,
- align: props.align || undefined,
- colspan: ifGreaterThanOne(props.colspan),
- rowspan: ifGreaterThanOne(props.rowspan),
- children: (props.children || []) as PhrasingContent[],
-});
-
-nodes.table_cell.attrsFromMyst = () => ({});
-nodes.table_cell.toMyst = (props: Props): TableCell => ({
- type: 'tableCell',
- header: undefined,
- align: props.align || undefined,
- colspan: ifGreaterThanOne(props.colspan),
- rowspan: ifGreaterThanOne(props.rowspan),
- children: (props.children || []) as PhrasingContent[],
-});
-
/**
* Create a "row" using a list-table
* ```text
diff --git a/packages/schema/src/nodes/time.ts b/packages/schema/src/nodes/time.ts
index 0e2539e5..9ba7c708 100644
--- a/packages/schema/src/nodes/time.ts
+++ b/packages/schema/src/nodes/time.ts
@@ -2,7 +2,7 @@ import { format } from 'date-fns';
import type { MdFormatSerialize, TexFormatSerialize } from '../serialize/types';
import type { Time } from '../spec';
import type { MyNodeSpec } from './types';
-import { NodeGroups } from './types';
+import { LEGACY_NODE_GROUPS } from './types';
export function getDatetime(object?: Date | string | null): Date {
if (object == null) {
@@ -34,7 +34,7 @@ export type Attrs = {
};
const time: MyNodeSpec = {
- group: NodeGroups.inline,
+ group: LEGACY_NODE_GROUPS.inline,
inline: true,
marks: '',
draggable: true,
diff --git a/packages/schema/src/nodes/types.ts b/packages/schema/src/nodes/types.ts
index 85700e6c..a756a458 100644
--- a/packages/schema/src/nodes/types.ts
+++ b/packages/schema/src/nodes/types.ts
@@ -20,24 +20,52 @@ export enum ReferenceKind {
table = 'table',
}
-export enum NodeGroups {
- 'top' = 'topblock',
- 'block' = 'block',
- 'heading' = 'heading',
- 'blockOrHeading' = '(block | heading)+',
- 'blockOrEquation' = '(block | equation)+',
- 'blockOrEquationOrHeading' = '(block | heading | equation)+',
- 'inline' = 'inline',
- 'text' = 'text',
- 'cite' = 'cite',
+export const LEGACY_NODE_GROUPS = {
+ top: 'topblock',
+ content: 'block',
+ heading: 'heading',
+ blockOrHeading: '(block | heading)+',
+ blockOrEquation: '(block | equation)+',
+ blockOrEquationOrHeading: '(block | heading | equation)+',
+ inline: 'inline',
+ text: 'text',
+ cite: 'cite',
// This allows you to drag images in/out and reposition the figure caption
// It does mean that the figure can be empty, which is not good
// We need to delete this empty figure in a post processing step
// This also allows two figure captions
// 'insideFigure' = 'figcaption{0,1} (image | code_block | iframe | table)* figcaption{0,1}',
// 'insideFigure' = '(figcaption | image | code_block | iframe | table)+',
- 'insideFigure' = '(figcaption | image | iframe | table | code_block){1,2}',
-}
+ insideFigure: '(figcaption | image | iframe | table | code_block){1,2}',
+} as const;
+
+export const ARTICLE_NODE_GROUPS = {
+ block: 'block', // NOTE: naming here needs some work (requirements: avoid collision)
+ top: 'topcontent',
+ content: 'content',
+ heading: 'heading',
+ blockOrHeading: '(content | heading)+',
+ blockOrEquation: '(content | equation)+',
+ blockOrEquationOrHeading: '(content | heading | equation)+',
+ inline: 'inline',
+ text: 'text',
+ cite: 'cite',
+ // This allows you to drag images in/out and reposition the figure caption
+ // It does mean that the figure can be empty, which is not good
+ // We need to delete this empty figure in a post processing step
+ // This also allows two figure captions
+ // 'insideFigure' = 'figcaption{0,1} (image | code_block | iframe | table)* figcaption{0,1}',
+ // 'insideFigure' = '(figcaption | image | code_block | iframe | table)+',
+ insideFigure: '(figcaption | image | iframe | table | code_block){1,2}',
+} as const;
+
+export type LegacyNodeGroup = typeof LEGACY_NODE_GROUPS;
+export type NextNodeGroup = typeof ARTICLE_NODE_GROUPS;
+export type NodeGroup = LegacyNodeGroup | NextNodeGroup;
+export const NODE_GROUPS = {
+ article: ARTICLE_NODE_GROUPS,
+ legacy: LEGACY_NODE_GROUPS,
+};
export enum MarkGroups {
'format' = 'format',
@@ -84,5 +112,5 @@ export type NodeDef = {
mystType: string;
attrs: Attr[];
inline: boolean;
- group: NodeGroups;
+ group: string;
};
diff --git a/packages/schema/src/nodes/utils.ts b/packages/schema/src/nodes/utils.ts
index d12df892..b0b90ffb 100644
--- a/packages/schema/src/nodes/utils.ts
+++ b/packages/schema/src/nodes/utils.ts
@@ -4,7 +4,7 @@ import { DEFAULT_IMAGE_WIDTH } from '../defaults';
import type { MdSerializerState } from '../types';
import { nodeNames } from '../types';
import { clamp, createId } from '../utils';
-import type { NodeSpecAttrs, NumberedNode } from './types';
+import type { NodeDef, NodeGroup, NodeSpecAttrs, NumberedNode } from './types';
export const getImageWidth = (width?: number | string | null) => {
if (typeof width === 'number') {
@@ -203,3 +203,11 @@ export function flattenValues(node: GenericNode): string {
}
return '';
}
+
+export function buildDef(
+ nodeGroup: NodeGroup,
+ def: Omit,
+ groupOption: keyof NodeGroup = 'inline',
+) {
+ return { ...def, group: nodeGroup[groupOption] } as NodeDef;
+}
diff --git a/packages/schema/src/process/modifyTransactions.ts b/packages/schema/src/process/modifyTransactions.ts
index 0874f5d2..e6aeb940 100644
--- a/packages/schema/src/process/modifyTransactions.ts
+++ b/packages/schema/src/process/modifyTransactions.ts
@@ -76,6 +76,7 @@ export function modifyTransactionToValidDocState(tr: Transaction): Transaction {
return false;
}
case nodeNames.heading:
+ case nodeNames.block:
case nodeNames.equation: {
const { id } = node.attrs as NumberedNode;
const nextId = ensureIdIsValidOrCreateId(id);
diff --git a/packages/schema/src/schemas.ts b/packages/schema/src/schemas.ts
index bcd2b520..a40fa678 100644
--- a/packages/schema/src/schemas.ts
+++ b/packages/schema/src/schemas.ts
@@ -1,74 +1,123 @@
import { Schema } from 'prosemirror-model';
import * as basic from './nodes/basic';
-import { nodes as tableNodes } from './nodes/table';
+import { createTableNodeSpec } from './nodes/table';
import * as basicMarks from './marks';
import * as Nodes from './nodes';
+import { nodeNames } from './types';
+import { createFigureNodeSpec } from './nodes/figure';
+import { createImageNodeSpec } from './nodes/image';
+import type { NodeGroup } from './nodes/types';
+import { LEGACY_NODE_GROUPS, ARTICLE_NODE_GROUPS } from './nodes/types';
+import { createCodeBlockSpec } from './nodes/code';
-export const listNodes = {
- ordered_list: basic.ordered_list,
- bullet_list: basic.bullet_list,
- list_item: basic.list_item,
-};
-
-export const presentationalNodes = {
- aside: Nodes.Aside.default,
- callout: Nodes.Callout.default,
- link_block: Nodes.LinkBlock.default,
- iframe: Nodes.IFrame.default,
-};
+export function createListNodeSpec(nodeGroup: NodeGroup) {
+ const basicNodeSpecs = basic.createBasicNodeSpecs(nodeGroup);
+ return {
+ ordered_list: basicNodeSpecs.ordered_list,
+ bullet_list: basicNodeSpecs.bullet_list,
+ list_item: basicNodeSpecs.list_item,
+ };
+}
-export const citationNodes = {
- cite: Nodes.Cite.default,
- cite_group: Nodes.CiteGroup.default,
-};
+export function createPresentationNodeSpec(nodeGroup: NodeGroup) {
+ return {
+ aside: Nodes.Aside.createAsideNodeSpec(nodeGroup),
+ callout: Nodes.Callout.createCalloutNodeSpec(nodeGroup),
+ link_block: Nodes.LinkBlock.createLinkBlockNodeSpec(nodeGroup),
+ iframe: Nodes.IFrame.createIframeSpec(nodeGroup),
+ };
+}
-export const mathNodes = {
- math: Nodes.Math.default,
- equation: Nodes.Equation.default,
-};
-export const mathNodesNoDisplay = {
- math: Nodes.Math.mathNoDisplay,
- equation: Nodes.Equation.equationNoDisplay,
-};
+export function createCitationNodeSpec(nodeGroup: NodeGroup) {
+ return {
+ cite: Nodes.Cite.createCiteNodeSpec(nodeGroup),
+ cite_group: Nodes.CiteGroup.createCiteGroup(nodeGroup),
+ };
+}
-export const reactiveDisplayNodes = {
- // Does NOT include variable definitions
- display: Nodes.Display.default,
- dynamic: Nodes.Dynamic.default,
- range: Nodes.Range.default,
- switch: Nodes.Switch.default,
- button: Nodes.Button.default,
-};
+export function createMathNodeSpecNoDisplay(nodeGroup: NodeGroup) {
+ return {
+ math: Nodes.Math.createMathNodeSpec(nodeGroup).mathNoDisplay,
+ equation: Nodes.Equation.createEquationNodeSpec(nodeGroup).equationNoDisplay,
+ };
+}
+export function createMathNodeSpec(nodeGroup: NodeGroup) {
+ return {
+ math: Nodes.Math.createMathNodeSpec(nodeGroup).math,
+ equation: Nodes.Equation.createEquationNodeSpec(nodeGroup).equation,
+ };
+}
+export function createReactiveDisplayNodes(nodeGroup: NodeGroup) {
+ return {
+ display: Nodes.Display.createNodeSpec(nodeGroup),
+ dynamic: Nodes.Dynamic.createNodeSpec(nodeGroup),
+ range: Nodes.Range.createNodeSpec(nodeGroup),
+ switch: Nodes.Switch.createNodeSpec(nodeGroup),
+ button: Nodes.Button.createNodeSpec(nodeGroup),
+ };
+}
-export const reactiveNodes = {
- variable: Nodes.Variable.default,
- ...reactiveDisplayNodes,
-};
+export function createReactiveNodeSpec(nodeGroup: NodeGroup) {
+ return {
+ variable: Nodes.Variable.createNodeSpec(nodeGroup),
+ ...createReactiveDisplayNodes(nodeGroup),
+ };
+}
+export function createArticleNodeSpecs() {
+ const nodeGroup = ARTICLE_NODE_GROUPS;
+ const specs = basic.createBasicNodeSpecs(nodeGroup);
+ return {
+ doc: basic.docWithBlock,
+ [nodeNames.block]: basic.block,
+ text: specs.text,
+ paragraph: specs.paragraph,
+ heading: Nodes.Heading.createHeadingNodeSpec(nodeGroup),
+ footnote: Nodes.Footnote.default,
+ blockquote: specs.blockquote,
+ code_block: createCodeBlockSpec(nodeGroup),
+ figure: createFigureNodeSpec(nodeGroup),
+ figcaption: Nodes.Figcaption.default,
+ image: createImageNodeSpec(nodeGroup),
+ horizontal_rule: specs.horizontal_rule,
+ hard_break: specs.hard_break,
+ time: Nodes.Time.default,
+ ...createListNodeSpec(nodeGroup),
+ ...createTableNodeSpec(nodeGroup),
+ // Presentational components
+ ...createPresentationNodeSpec(nodeGroup),
+ ...createCitationNodeSpec(nodeGroup),
+ ...createMathNodeSpec(nodeGroup),
+ ...createReactiveNodeSpec(nodeGroup),
+ };
+}
-export const nodes = {
- // Basic markdown
- doc: basic.doc,
- text: basic.text,
- paragraph: basic.paragraph,
- heading: Nodes.Heading.default,
- footnote: Nodes.Footnote.default,
- blockquote: basic.blockquote,
- code_block: Nodes.Code.default,
- figure: Nodes.Figure.default,
- figcaption: Nodes.Figcaption.default,
- image: Nodes.Image.default,
- horizontal_rule: basic.horizontal_rule,
- hard_break: basic.hard_break,
- time: Nodes.Time.default,
- ...listNodes,
- ...tableNodes,
- // Presentational components
- ...presentationalNodes,
- ...citationNodes,
- ...mathNodes,
- ...reactiveNodes,
-};
+export function createLegacyNodeSpecs() {
+ const nodeGroup = LEGACY_NODE_GROUPS;
+ const specs = basic.createBasicNodeSpecs(nodeGroup);
+ return {
+ doc: basic.doc,
+ text: specs.text,
+ paragraph: specs.paragraph,
+ heading: Nodes.Heading.createHeadingNodeSpec(nodeGroup),
+ footnote: Nodes.Footnote.default,
+ blockquote: specs.blockquote,
+ code_block: createCodeBlockSpec(nodeGroup),
+ figure: createFigureNodeSpec(nodeGroup),
+ figcaption: Nodes.Figcaption.default,
+ image: createImageNodeSpec(nodeGroup),
+ horizontal_rule: specs.horizontal_rule,
+ hard_break: specs.hard_break,
+ time: Nodes.Time.default,
+ ...createListNodeSpec(nodeGroup),
+ ...createTableNodeSpec(nodeGroup),
+ // Presentational components
+ ...createPresentationNodeSpec(nodeGroup),
+ ...createCitationNodeSpec(nodeGroup),
+ ...createMathNodeSpec(nodeGroup),
+ ...createReactiveNodeSpec(nodeGroup),
+ };
+}
export const marks = {
link: basicMarks.link,
@@ -82,52 +131,64 @@ export const marks = {
abbr: basicMarks.abbr,
};
-export const presets = {
- full: {
- nodes,
- marks,
- },
- paragraph: {
- nodes: {
- doc: basic.docParagraph,
- paragraph: basic.paragraph,
- text: basic.text,
- hard_break: basic.hard_break,
- time: Nodes.Time.default,
- footnote: Nodes.Footnote.default,
- ...citationNodes,
- math: mathNodes.math,
- ...reactiveDisplayNodes,
+export function createPresets() {
+ const legacyBasicNodeSpecs = basic.createBasicNodeSpecs(LEGACY_NODE_GROUPS);
+ const legacyNodeSpecs = createLegacyNodeSpecs();
+ const articleNodeSpecs = createArticleNodeSpecs();
+ return {
+ article: {
+ nodes: articleNodeSpecs,
+ marks,
},
- marks,
- },
- comment: {
- nodes: {
- doc: basic.docComment,
- paragraph: basic.paragraph,
- heading: Nodes.Heading.default,
- text: basic.text,
- blockquote: basic.blockquote,
- footnote: Nodes.Footnote.default,
- code_block: Nodes.Code.default,
- horizontal_rule: basic.horizontal_rule,
- hard_break: basic.hard_break,
- time: Nodes.Time.default,
- mention: Nodes.Mention.default,
- ...listNodes,
- ...citationNodes,
- ...mathNodesNoDisplay,
+ full: {
+ nodes: legacyNodeSpecs,
+ marks,
},
- marks,
- },
-};
+ paragraph: {
+ nodes: {
+ doc: legacyBasicNodeSpecs.docParagraph,
+ paragraph: legacyNodeSpecs.paragraph,
+ text: legacyNodeSpecs.text,
+ hard_break: legacyNodeSpecs.hard_break,
+ time: Nodes.Time.default,
+ footnote: Nodes.Footnote.default,
+ ...createCitationNodeSpec(LEGACY_NODE_GROUPS),
+ math: createMathNodeSpec(LEGACY_NODE_GROUPS).math,
+ ...createReactiveDisplayNodes(LEGACY_NODE_GROUPS),
+ },
+ marks,
+ },
+ comment: {
+ nodes: {
+ doc: legacyBasicNodeSpecs.docComment,
+ paragraph: legacyNodeSpecs.paragraph,
+ heading: Nodes.Heading.createHeadingNodeSpec(LEGACY_NODE_GROUPS),
+ text: legacyNodeSpecs.text,
+ blockquote: legacyNodeSpecs.blockquote,
+ footnote: Nodes.Footnote.default,
+ code_block: createCodeBlockSpec(LEGACY_NODE_GROUPS),
+ horizontal_rule: legacyNodeSpecs.horizontal_rule,
+ hard_break: legacyNodeSpecs.hard_break,
+ time: Nodes.Time.default,
+ mention: Nodes.Mention.default,
+ ...createListNodeSpec(LEGACY_NODE_GROUPS),
+ ...createCitationNodeSpec(LEGACY_NODE_GROUPS),
+ ...createMathNodeSpecNoDisplay(LEGACY_NODE_GROUPS),
+ },
+ marks,
+ },
+ };
+}
-export type PresetSchemas = keyof typeof presets;
+export type PresetSchemas = keyof ReturnType;
export type UseSchema = PresetSchemas | { nodes: Record } | Schema;
export function getSchema(useSchema: UseSchema) {
+ const presets = createPresets();
if (typeof useSchema === 'string') {
switch (useSchema) {
+ case 'article':
+ return new Schema(presets.article);
case 'full':
return new Schema(presets.full);
case 'paragraph':
diff --git a/packages/schema/src/serialize/html/index.ts b/packages/schema/src/serialize/html/index.ts
index dcdfb1e5..7372b37f 100644
--- a/packages/schema/src/serialize/html/index.ts
+++ b/packages/schema/src/serialize/html/index.ts
@@ -10,3 +10,11 @@ export function toHTML(doc: ProsemirrorNode, useSchema: UseSchema, document: Doc
div.appendChild(frag);
return div.innerHTML;
}
+
+export function toHTMLAsNode(doc: ProsemirrorNode, useSchema: UseSchema, document: Document) {
+ const schema = getSchema(useSchema);
+ const div = document.createElement('div');
+ const frag = DOMSerializer.fromSchema(schema).serializeNode(doc, { document });
+ div.appendChild(frag);
+ return div.innerHTML;
+}
diff --git a/packages/schema/src/serialize/index.ts b/packages/schema/src/serialize/index.ts
index 3c9693bd..b79356f8 100644
--- a/packages/schema/src/serialize/index.ts
+++ b/packages/schema/src/serialize/index.ts
@@ -2,7 +2,7 @@ export { toMdast } from './mdast';
export { toMarkdown, toMyst } from './markdown';
export { toTex } from './tex';
export { toText } from './text';
-export { toHTML } from './html';
+export { toHTML, toHTMLAsNode } from './html';
export { TexFormatTypes, SharedMarkdownOptions } from './types';
export { toJSON } from './json';
export { toYAML } from './yaml';
diff --git a/packages/schema/src/serialize/markdown/index.ts b/packages/schema/src/serialize/markdown/index.ts
index 3b656d23..da0aae13 100644
--- a/packages/schema/src/serialize/markdown/index.ts
+++ b/packages/schema/src/serialize/markdown/index.ts
@@ -6,6 +6,7 @@ import * as nodes from '../../nodes';
import type { MarkdownOptions, MdSerializerState } from '../types';
import { cleanWhitespaceChars } from '../clean';
import { toMdastSnippet } from '../mdast';
+import { nodeNames } from '../../types';
type MarkdownSerializerParameters = ConstructorParameters;
@@ -19,6 +20,9 @@ const mdNodes: MarkdownSerializerParameters[0] = {
text(state, node) {
state.text(cleanWhitespaceChars(node.text ?? ''));
},
+ [nodeNames.block](state, node) {
+ state.text('not supported!!');
+ },
paragraph(state, node) {
state.renderInline(node);
state.closeBlock(node);
diff --git a/packages/schema/src/serialize/tex/index.ts b/packages/schema/src/serialize/tex/index.ts
index 169e7edb..0bd5001c 100644
--- a/packages/schema/src/serialize/tex/index.ts
+++ b/packages/schema/src/serialize/tex/index.ts
@@ -90,6 +90,9 @@ export const texSerializer = new MarkdownSerializer(
},
image: nodes.Image.toTex,
figure: nodes.Figure.toTex,
+ [nodeNames.block](state, node) {
+ state.text('not supported!!');
+ },
figcaption: nodes.Figcaption.toTex,
footnote: nodes.Footnote.toTex,
iframe: blankTexLines,
diff --git a/packages/schema/src/serialize/text/index.ts b/packages/schema/src/serialize/text/index.ts
index aa7052e7..5cefdf21 100644
--- a/packages/schema/src/serialize/text/index.ts
+++ b/packages/schema/src/serialize/text/index.ts
@@ -3,6 +3,7 @@ import { MarkdownSerializer } from 'prosemirror-markdown';
import type { Node as ProsemirrorNode } from 'prosemirror-model';
import * as nodes from '../../nodes';
import { cleanWhitespaceChars } from '../clean';
+import { nodeNames } from '../../types';
function simpleInlineRender(state: MarkdownSerializerState, node: ProsemirrorNode) {
state.renderInline(node);
@@ -62,6 +63,7 @@ export const textSerializer = new MarkdownSerializer(
figcaption: simpleBlockRender,
time: nodes.Time.toMarkdown, // This is just the time! :)
callout: simpleBlockRender,
+ [nodeNames.block]: simpleBlockRender,
aside: simpleBlockRender,
// Technical
math: simpleInlineRender,
diff --git a/packages/schema/src/types.ts b/packages/schema/src/types.ts
index 025a5488..0bf0c9ec 100644
--- a/packages/schema/src/types.ts
+++ b/packages/schema/src/types.ts
@@ -7,6 +7,7 @@ export * from './serialize/types';
export type { LinkAttrs, AlignOptions };
export enum nodeNames {
+ block = 'block',
text = 'text',
paragraph = 'paragraph',
heading = 'heading',
diff --git a/packages/schema/test/build.ts b/packages/schema/test/build.ts
index e81de2d7..68d75b5c 100644
--- a/packages/schema/test/build.ts
+++ b/packages/schema/test/build.ts
@@ -1,9 +1,10 @@
/* eslint-disable import/no-extraneous-dependencies */
-import { Node as ProsemirrorNode, Schema } from 'prosemirror-model';
+import type { Node as ProsemirrorNode } from 'prosemirror-model';
+import { Schema } from 'prosemirror-model';
import { builders } from 'prosemirror-test-builder';
import * as src from '../src';
-const schema = new Schema(src.schemas.presets.full);
+const schema = new Schema(src.schemas.createPresets().full);
export const tnodes = builders(schema, {
p: { nodeType: 'paragraph' },
diff --git a/packages/schema/tsconfig.base.json b/packages/schema/tsconfig.base.json
index 665025c2..5a82c61b 100644
--- a/packages/schema/tsconfig.base.json
+++ b/packages/schema/tsconfig.base.json
@@ -5,11 +5,11 @@
"target": "es6",
"noImplicitAny": true,
"strict": true,
+ "baseUrl": "src",
"moduleResolution": "node",
"lib": ["es2019", "DOM"],
"sourceMap": true,
"outDir": "dist/cjs",
- "baseUrl": "src",
"paths": {
"*": ["node_modules/*", "types/*"]
},