Compare commits
3 Commits
f462205463
...
6461039bd7
| Author | SHA1 | Date | |
|---|---|---|---|
| 6461039bd7 | |||
| 0ba87ac547 | |||
| 406641c522 |
6
.githooks/pre-commit
Executable file
6
.githooks/pre-commit
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Run TypeScript type-checking before each commit.
|
||||||
|
# Babel strips types without checking them, so this is what actually
|
||||||
|
# enforces type correctness. Bypass with `git commit --no-verify`.
|
||||||
|
|
||||||
|
npm run typecheck
|
||||||
7
Justfile
7
Justfile
@@ -20,8 +20,9 @@ deploy: build
|
|||||||
|
|
||||||
format:
|
format:
|
||||||
npx prettier --write books/
|
npx prettier --write books/
|
||||||
npx prettier --write fabula-ultima-sheet.js
|
npx prettier --write src/
|
||||||
npx prettier --write webpack.config.js
|
npx prettier --write webpack.config.js
|
||||||
npx prettier --write fabula-ultima-sheet.css
|
|
||||||
npx prettier --write fabula-ultima-sheet.html
|
typecheck:
|
||||||
|
npm run typecheck
|
||||||
|
|
||||||
|
|||||||
209
package-lock.json
generated
209
package-lock.json
generated
@@ -7,7 +7,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.29.7",
|
"@babel/core": "^7.29.7",
|
||||||
"@babel/preset-react": "^7.29.7",
|
"@babel/preset-react": "^7.29.7",
|
||||||
|
"@babel/preset-typescript": "^7.29.7",
|
||||||
"@babel/register": "^7.29.7",
|
"@babel/register": "^7.29.7",
|
||||||
|
"@types/react": "^19.2.17",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"babel-loader": "^10.1.1",
|
"babel-loader": "^10.1.1",
|
||||||
"copy-webpack-plugin": "^14.0.0",
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
"css-loader": "^7.1.4",
|
"css-loader": "^7.1.4",
|
||||||
@@ -18,6 +21,7 @@
|
|||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
"style-loader": "^4.0.0",
|
"style-loader": "^4.0.0",
|
||||||
|
"typescript": "^6.0.3",
|
||||||
"webpack": "^5.107.2",
|
"webpack": "^5.107.2",
|
||||||
"webpack-cli": "^7.0.3",
|
"webpack-cli": "^7.0.3",
|
||||||
"webpack-dev-server": "^5.2.4"
|
"webpack-dev-server": "^5.2.4"
|
||||||
@@ -171,6 +175,38 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-annotate-as-pure": "^7.29.7",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.29.7",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.29.7",
|
||||||
|
"@babel/helper-replace-supers": "^7.29.7",
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
|
||||||
|
"@babel/traverse": "^7.29.7",
|
||||||
|
"semver": "^6.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-globals": {
|
"node_modules/@babel/helper-globals": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
|
||||||
@@ -181,6 +217,20 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/traverse": "^7.29.7",
|
||||||
|
"@babel/types": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-module-imports": {
|
"node_modules/@babel/helper-module-imports": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
|
||||||
@@ -213,6 +263,19 @@
|
|||||||
"@babel/core": "^7.0.0"
|
"@babel/core": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-optimise-call-expression": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-plugin-utils": {
|
"node_modules/@babel/helper-plugin-utils": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
|
||||||
@@ -223,6 +286,38 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-replace-supers": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.29.7",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.29.7",
|
||||||
|
"@babel/traverse": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/traverse": "^7.29.7",
|
||||||
|
"@babel/types": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||||
@@ -299,6 +394,39 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/plugin-syntax-typescript": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-modules-commonjs": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-transforms": "^7.29.7",
|
||||||
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/plugin-transform-react-display-name": {
|
"node_modules/@babel/plugin-transform-react-display-name": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz",
|
||||||
@@ -368,6 +496,26 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-typescript": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-annotate-as-pure": "^7.29.7",
|
||||||
|
"@babel/helper-create-class-features-plugin": "^7.29.7",
|
||||||
|
"@babel/helper-plugin-utils": "^7.29.7",
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
|
||||||
|
"@babel/plugin-syntax-typescript": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/preset-react": {
|
"node_modules/@babel/preset-react": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz",
|
||||||
@@ -389,6 +537,26 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/preset-typescript": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.29.7",
|
||||||
|
"@babel/helper-validator-option": "^7.29.7",
|
||||||
|
"@babel/plugin-syntax-jsx": "^7.29.7",
|
||||||
|
"@babel/plugin-transform-modules-commonjs": "^7.29.7",
|
||||||
|
"@babel/plugin-transform-typescript": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/register": {
|
"node_modules/@babel/register": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.7.tgz",
|
||||||
@@ -1396,6 +1564,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "19.2.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
|
||||||
|
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "19.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^19.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/retry": {
|
"node_modules/@types/retry": {
|
||||||
"version": "0.12.2",
|
"version": "0.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
|
||||||
@@ -2751,6 +2939,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "CC0-1.0"
|
"license": "CC0-1.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
@@ -6587,6 +6782,20 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --mode=production",
|
"build": "webpack --mode=production",
|
||||||
"dev": "webpack serve --mode=development"
|
"dev": "webpack serve --mode=development",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"prepare": "git config core.hooksPath .githooks || true"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.29.7",
|
"@babel/core": "^7.29.7",
|
||||||
"@babel/preset-react": "^7.29.7",
|
"@babel/preset-react": "^7.29.7",
|
||||||
|
"@babel/preset-typescript": "^7.29.7",
|
||||||
"@babel/register": "^7.29.7",
|
"@babel/register": "^7.29.7",
|
||||||
|
"@types/react": "^19.2.17",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"babel-loader": "^10.1.1",
|
"babel-loader": "^10.1.1",
|
||||||
"copy-webpack-plugin": "^14.0.0",
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
"css-loader": "^7.1.4",
|
"css-loader": "^7.1.4",
|
||||||
@@ -17,6 +22,7 @@
|
|||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
"style-loader": "^4.0.0",
|
"style-loader": "^4.0.0",
|
||||||
|
"typescript": "^6.0.3",
|
||||||
"webpack": "^5.107.2",
|
"webpack": "^5.107.2",
|
||||||
"webpack-cli": "^7.0.3",
|
"webpack-cli": "^7.0.3",
|
||||||
"webpack-dev-server": "^5.2.4"
|
"webpack-dev-server": "^5.2.4"
|
||||||
|
|||||||
BIN
pdf/Fabula_Ultima_-_Natural_Fantasy_Atlas_ENG_v1_1.pdf
Normal file
BIN
pdf/Fabula_Ultima_-_Natural_Fantasy_Atlas_ENG_v1_1.pdf
Normal file
Binary file not shown.
BIN
pdf/Fabula_Ultima_TTJRPG.pdf
Normal file
BIN
pdf/Fabula_Ultima_TTJRPG.pdf
Normal file
Binary file not shown.
@@ -1,6 +1,12 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
export default function BookIndex({ title, logoText, pages }) {
|
interface BookIndexProps {
|
||||||
|
title: string;
|
||||||
|
logoText: string;
|
||||||
|
pages: BookPage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
|
||||||
const pageNums = useMemo(() => pages.map(p => p.n), [pages]);
|
const pageNums = useMemo(() => pages.map(p => p.n), [pages]);
|
||||||
const total = pageNums.length;
|
const total = pageNums.length;
|
||||||
|
|
||||||
@@ -13,9 +19,9 @@ export default function BookIndex({ title, logoText, pages }) {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentRef = useRef(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const goTo = useCallback((n, smooth, push) => {
|
const goTo = useCallback((n: number, smooth: boolean, push: boolean) => {
|
||||||
const idx = pageNums.indexOf(n);
|
const idx = pageNums.indexOf(n);
|
||||||
if (idx === -1) return;
|
if (idx === -1) return;
|
||||||
const sec = document.getElementById(`page-${n}`);
|
const sec = document.getElementById(`page-${n}`);
|
||||||
@@ -62,8 +68,9 @@ export default function BookIndex({ title, logoText, pages }) {
|
|||||||
|
|
||||||
// Keyboard navigation
|
// Keyboard navigation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = e => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
const tag = (e.target as HTMLElement).tagName;
|
||||||
|
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
|
||||||
setCurrentIdx(prev => {
|
setCurrentIdx(prev => {
|
||||||
if ((e.key === 'ArrowLeft' || e.key === 'ArrowUp') && prev > 0) {
|
if ((e.key === 'ArrowLeft' || e.key === 'ArrowUp') && prev > 0) {
|
||||||
goTo(pageNums[prev - 1], true, true);
|
goTo(pageNums[prev - 1], true, true);
|
||||||
@@ -25,7 +25,71 @@ const DISCIPLINES = [
|
|||||||
"Spiritism",
|
"Spiritism",
|
||||||
];
|
];
|
||||||
|
|
||||||
const BLANK_FIELDS = {
|
interface Fields {
|
||||||
|
charName: string;
|
||||||
|
charPronouns: string;
|
||||||
|
charIdentity: string;
|
||||||
|
charTheme: string;
|
||||||
|
charOrigin: string;
|
||||||
|
charTraits: string;
|
||||||
|
xpCurrent: string | number;
|
||||||
|
zenit: string | number;
|
||||||
|
initMod: string | number;
|
||||||
|
defense: string | number;
|
||||||
|
magDef: string | number;
|
||||||
|
dexBase: string | number;
|
||||||
|
dexCur: string | number;
|
||||||
|
insBase: string | number;
|
||||||
|
insCur: string | number;
|
||||||
|
migBase: string | number;
|
||||||
|
migCur: string | number;
|
||||||
|
wlpBase: string | number;
|
||||||
|
wlpCur: string | number;
|
||||||
|
hpMax: string | number;
|
||||||
|
hpCur: string | number;
|
||||||
|
mpMax: string | number;
|
||||||
|
mpCur: string | number;
|
||||||
|
ipMax: string | number;
|
||||||
|
ipCur: string | number;
|
||||||
|
backpack: string;
|
||||||
|
heroicSkills: string;
|
||||||
|
ritualsNotes: string;
|
||||||
|
accName: string;
|
||||||
|
accDesc: string;
|
||||||
|
armName: string;
|
||||||
|
armDesc: string;
|
||||||
|
mhName: string;
|
||||||
|
mhDesc: string;
|
||||||
|
ohName: string;
|
||||||
|
ohDesc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Bond {
|
||||||
|
name: string;
|
||||||
|
feelings: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClassEntry {
|
||||||
|
name: string;
|
||||||
|
benefits: string;
|
||||||
|
skills: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Spell {
|
||||||
|
name: string;
|
||||||
|
notes: string;
|
||||||
|
mp: string | number;
|
||||||
|
targets: string;
|
||||||
|
duration: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckMap = Record<string, boolean>;
|
||||||
|
|
||||||
|
// Saved/shared payloads use abbreviated keys (with legacy long-name
|
||||||
|
// fallbacks), so the shape is intentionally loose.
|
||||||
|
type SavedData = Record<string, any>;
|
||||||
|
|
||||||
|
const BLANK_FIELDS: Fields = {
|
||||||
charName: "",
|
charName: "",
|
||||||
charPronouns: "",
|
charPronouns: "",
|
||||||
charIdentity: "",
|
charIdentity: "",
|
||||||
@@ -64,12 +128,12 @@ const BLANK_FIELDS = {
|
|||||||
ohDesc: "",
|
ohDesc: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const BLANK_BONDS = Array.from({ length: 6 }, () => ({
|
const BLANK_BONDS: Bond[] = Array.from({ length: 6 }, () => ({
|
||||||
name: "",
|
name: "",
|
||||||
feelings: [],
|
feelings: [],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
async function compressToBase64(str) {
|
async function compressToBase64(str: string) {
|
||||||
const stream = new CompressionStream("deflate-raw");
|
const stream = new CompressionStream("deflate-raw");
|
||||||
const writer = stream.writable.getWriter();
|
const writer = stream.writable.getWriter();
|
||||||
writer.write(new TextEncoder().encode(str));
|
writer.write(new TextEncoder().encode(str));
|
||||||
@@ -82,7 +146,7 @@ async function compressToBase64(str) {
|
|||||||
return btoa(binary);
|
return btoa(binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decompressFromBase64(b64) {
|
async function decompressFromBase64(b64: string) {
|
||||||
try {
|
try {
|
||||||
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
||||||
const stream = new DecompressionStream("deflate-raw");
|
const stream = new DecompressionStream("deflate-raw");
|
||||||
@@ -103,15 +167,15 @@ export default function CharacterSheet() {
|
|||||||
const [copyStatus, setCopyStatus] = useState(false);
|
const [copyStatus, setCopyStatus] = useState(false);
|
||||||
const [level, setLevel] = useState(1);
|
const [level, setLevel] = useState(1);
|
||||||
const [fp, setFp] = useState(0);
|
const [fp, setFp] = useState(0);
|
||||||
const [fields, setFields] = useState(BLANK_FIELDS);
|
const [fields, setFields] = useState<Fields>(BLANK_FIELDS);
|
||||||
const [bonds, setBonds] = useState(BLANK_BONDS);
|
const [bonds, setBonds] = useState<Bond[]>(BLANK_BONDS);
|
||||||
const [primaryClasses, setPrimaryClasses] = useState([]);
|
const [primaryClasses, setPrimaryClasses] = useState<ClassEntry[]>([]);
|
||||||
const [otherClasses, setOtherClasses] = useState([]);
|
const [otherClasses, setOtherClasses] = useState<ClassEntry[]>([]);
|
||||||
const [spells, setSpells] = useState([]);
|
const [spells, setSpells] = useState<Spell[]>([]);
|
||||||
const [statuses, setStatuses] = useState({});
|
const [statuses, setStatuses] = useState<CheckMap>({});
|
||||||
const [martial, setMartial] = useState({});
|
const [martial, setMartial] = useState<CheckMap>({});
|
||||||
const [disciplines, setDisciplines] = useState({});
|
const [disciplines, setDisciplines] = useState<CheckMap>({});
|
||||||
const [theme, setTheme] = useState(
|
const [theme, setTheme] = useState<string>(
|
||||||
() =>
|
() =>
|
||||||
localStorage.getItem("fabulaUltimaTheme") ||
|
localStorage.getItem("fabulaUltimaTheme") ||
|
||||||
(window.matchMedia("(prefers-color-scheme: light)").matches
|
(window.matchMedia("(prefers-color-scheme: light)").matches
|
||||||
@@ -119,7 +183,7 @@ export default function CharacterSheet() {
|
|||||||
: "dark"),
|
: "dark"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const importFileRef = useRef(null);
|
const importFileRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
@@ -127,14 +191,15 @@ export default function CharacterSheet() {
|
|||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
const f = useCallback(
|
const f = useCallback(
|
||||||
(key, val) => setFields((prev) => ({ ...prev, [key]: val })),
|
<K extends keyof Fields>(key: K, val: Fields[K]) =>
|
||||||
|
setFields((prev) => ({ ...prev, [key]: val })),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const xp = parseInt(fields.xpCurrent) || 0;
|
const xp = parseInt(String(fields.xpCurrent)) || 0;
|
||||||
const xpPct = Math.min((xp % 10) * 10, 100);
|
const xpPct = Math.min((xp % 10) * 10, 100);
|
||||||
const hpMax = parseInt(fields.hpMax) || 0;
|
const hpMax = parseInt(String(fields.hpMax)) || 0;
|
||||||
const hpCur = parseInt(fields.hpCur) || 0;
|
const hpCur = parseInt(String(fields.hpCur)) || 0;
|
||||||
const inCrisis = hpMax > 0 && hpCur <= Math.floor(hpMax / 2);
|
const inCrisis = hpMax > 0 && hpCur <= Math.floor(hpMax / 2);
|
||||||
const fpTotal = Math.max(10, fp);
|
const fpTotal = Math.max(10, fp);
|
||||||
|
|
||||||
@@ -210,7 +275,7 @@ export default function CharacterSheet() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const applyData = useCallback((d) => {
|
const applyData = useCallback((d: SavedData) => {
|
||||||
setFields({
|
setFields({
|
||||||
charName: d.n ?? d.name ?? "",
|
charName: d.n ?? d.name ?? "",
|
||||||
charPronouns: d.pn ?? d.pronouns ?? "",
|
charPronouns: d.pn ?? d.pronouns ?? "",
|
||||||
@@ -266,7 +331,7 @@ export default function CharacterSheet() {
|
|||||||
const rawBonds = d.bo ?? d.bonds;
|
const rawBonds = d.bo ?? d.bonds;
|
||||||
if (rawBonds)
|
if (rawBonds)
|
||||||
setBonds(
|
setBonds(
|
||||||
rawBonds.map((b) => ({
|
rawBonds.map((b: SavedData) => ({
|
||||||
name: b.n ?? b.name ?? "",
|
name: b.n ?? b.name ?? "",
|
||||||
feelings: b.f ?? b.feelings ?? [],
|
feelings: b.f ?? b.feelings ?? [],
|
||||||
})),
|
})),
|
||||||
@@ -275,7 +340,7 @@ export default function CharacterSheet() {
|
|||||||
const rawPrimary = d.pc ?? d.primaryClasses;
|
const rawPrimary = d.pc ?? d.primaryClasses;
|
||||||
if (rawPrimary)
|
if (rawPrimary)
|
||||||
setPrimaryClasses(
|
setPrimaryClasses(
|
||||||
rawPrimary.map((c) => ({
|
rawPrimary.map((c: SavedData) => ({
|
||||||
name: c.n ?? c.name ?? "",
|
name: c.n ?? c.name ?? "",
|
||||||
benefits: c.b ?? c.benefits ?? "",
|
benefits: c.b ?? c.benefits ?? "",
|
||||||
skills: c.s ?? c.skills ?? "",
|
skills: c.s ?? c.skills ?? "",
|
||||||
@@ -285,7 +350,7 @@ export default function CharacterSheet() {
|
|||||||
const rawOther = d.oc ?? d.otherClasses;
|
const rawOther = d.oc ?? d.otherClasses;
|
||||||
if (rawOther)
|
if (rawOther)
|
||||||
setOtherClasses(
|
setOtherClasses(
|
||||||
rawOther.map((c) => ({
|
rawOther.map((c: SavedData) => ({
|
||||||
name: c.n ?? c.name ?? "",
|
name: c.n ?? c.name ?? "",
|
||||||
benefits: c.b ?? c.benefits ?? "",
|
benefits: c.b ?? c.benefits ?? "",
|
||||||
skills: c.s ?? c.skills ?? "",
|
skills: c.s ?? c.skills ?? "",
|
||||||
@@ -295,7 +360,7 @@ export default function CharacterSheet() {
|
|||||||
const rawSpells = d.sp ?? d.spells;
|
const rawSpells = d.sp ?? d.spells;
|
||||||
if (rawSpells)
|
if (rawSpells)
|
||||||
setSpells(
|
setSpells(
|
||||||
rawSpells.map((s) => ({
|
rawSpells.map((s: SavedData) => ({
|
||||||
name: s.n ?? s.name ?? "",
|
name: s.n ?? s.name ?? "",
|
||||||
notes: s.nt ?? s.notes ?? "",
|
notes: s.nt ?? s.notes ?? "",
|
||||||
mp: s.mp ?? "",
|
mp: s.mp ?? "",
|
||||||
@@ -369,13 +434,13 @@ export default function CharacterSheet() {
|
|||||||
}, [collectData]);
|
}, [collectData]);
|
||||||
|
|
||||||
const handleImportFile = useCallback(
|
const handleImportFile = useCallback(
|
||||||
(e) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (ev) => {
|
reader.onload = (ev) => {
|
||||||
try {
|
try {
|
||||||
applyData(JSON.parse(ev.target.result));
|
applyData(JSON.parse(ev.target?.result as string));
|
||||||
saveSheet();
|
saveSheet();
|
||||||
} catch {
|
} catch {
|
||||||
alert("Could not import: invalid JSON file.");
|
alert("Could not import: invalid JSON file.");
|
||||||
@@ -399,25 +464,25 @@ export default function CharacterSheet() {
|
|||||||
}, [collectData]);
|
}, [collectData]);
|
||||||
|
|
||||||
const calcHP = useCallback(() => {
|
const calcHP = useCallback(() => {
|
||||||
const mig = parseInt(fields.migBase) || 6;
|
const mig = parseInt(String(fields.migBase)) || 6;
|
||||||
const max = mig * 5 + level;
|
const max = mig * 5 + level;
|
||||||
setFields((prev) => ({ ...prev, hpMax: max, hpCur: prev.hpCur || max }));
|
setFields((prev) => ({ ...prev, hpMax: max, hpCur: prev.hpCur || max }));
|
||||||
}, [fields.migBase, level]);
|
}, [fields.migBase, level]);
|
||||||
|
|
||||||
const calcMP = useCallback(() => {
|
const calcMP = useCallback(() => {
|
||||||
const wlp = parseInt(fields.wlpBase) || 6;
|
const wlp = parseInt(String(fields.wlpBase)) || 6;
|
||||||
const max = wlp * 5 + level;
|
const max = wlp * 5 + level;
|
||||||
setFields((prev) => ({ ...prev, mpMax: max, mpCur: prev.mpCur || max }));
|
setFields((prev) => ({ ...prev, mpMax: max, mpCur: prev.mpCur || max }));
|
||||||
}, [fields.wlpBase, level]);
|
}, [fields.wlpBase, level]);
|
||||||
|
|
||||||
const toggleStatus = (s) =>
|
const toggleStatus = (s: string) =>
|
||||||
setStatuses((prev) => ({ ...prev, [s]: !prev[s] }));
|
setStatuses((prev) => ({ ...prev, [s]: !prev[s] }));
|
||||||
const toggleMartial = (m) =>
|
const toggleMartial = (m: string) =>
|
||||||
setMartial((prev) => ({ ...prev, [m]: !prev[m] }));
|
setMartial((prev) => ({ ...prev, [m]: !prev[m] }));
|
||||||
const toggleDisc = (d) =>
|
const toggleDisc = (d: string) =>
|
||||||
setDisciplines((prev) => ({ ...prev, [d]: !prev[d] }));
|
setDisciplines((prev) => ({ ...prev, [d]: !prev[d] }));
|
||||||
|
|
||||||
const toggleFeeling = (bondIdx, feeling) => {
|
const toggleFeeling = (bondIdx: number, feeling: string) => {
|
||||||
setBonds((prev) =>
|
setBonds((prev) =>
|
||||||
prev.map((b, i) => {
|
prev.map((b, i) => {
|
||||||
if (i !== bondIdx) return b;
|
if (i !== bondIdx) return b;
|
||||||
@@ -647,12 +712,12 @@ export default function CharacterSheet() {
|
|||||||
<span className="icon">◈</span> Attributes
|
<span className="icon">◈</span> Attributes
|
||||||
</div>
|
</div>
|
||||||
<div className="attr-grid">
|
<div className="attr-grid">
|
||||||
{[
|
{([
|
||||||
{ label: "Dexterity", base: "dexBase", cur: "dexCur" },
|
{ label: "Dexterity", base: "dexBase", cur: "dexCur" },
|
||||||
{ label: "Insight", base: "insBase", cur: "insCur" },
|
{ label: "Insight", base: "insBase", cur: "insCur" },
|
||||||
{ label: "Might", base: "migBase", cur: "migCur" },
|
{ label: "Might", base: "migBase", cur: "migCur" },
|
||||||
{ label: "Willpower", base: "wlpBase", cur: "wlpCur" },
|
{ label: "Willpower", base: "wlpBase", cur: "wlpCur" },
|
||||||
].map(({ label, base, cur }) => (
|
] as const).map(({ label, base, cur }) => (
|
||||||
<div key={label} className="attr-block">
|
<div key={label} className="attr-block">
|
||||||
<div className="attr-name">{label}</div>
|
<div className="attr-name">{label}</div>
|
||||||
<div className="attr-inputs">
|
<div className="attr-inputs">
|
||||||
@@ -917,7 +982,7 @@ export default function CharacterSheet() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 14 }}>
|
<div style={{ marginTop: 14 }}>
|
||||||
{[
|
{([
|
||||||
{
|
{
|
||||||
slot: "Accessory",
|
slot: "Accessory",
|
||||||
name: "accName",
|
name: "accName",
|
||||||
@@ -946,7 +1011,7 @@ export default function CharacterSheet() {
|
|||||||
namePh: "Weapon / shield",
|
namePh: "Weapon / shield",
|
||||||
descPh: "Damage / effect",
|
descPh: "Damage / effect",
|
||||||
},
|
},
|
||||||
].map(({ slot, name, desc, namePh, descPh }) => (
|
] as const).map(({ slot, name, desc, namePh, descPh }) => (
|
||||||
<div key={slot} className="equip-row">
|
<div key={slot} className="equip-row">
|
||||||
<div className="equip-slot">{slot}</div>
|
<div className="equip-slot">{slot}</div>
|
||||||
<div className="equip-fields">
|
<div className="equip-fields">
|
||||||
@@ -1346,6 +1411,7 @@ export default function CharacterSheet() {
|
|||||||
<button
|
<button
|
||||||
className="btn-load btn-import btn-lg"
|
className="btn-load btn-import btn-lg"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (!importFileRef.current) return;
|
||||||
importFileRef.current.value = "";
|
importFileRef.current.value = "";
|
||||||
importFileRef.current.click();
|
importFileRef.current.click();
|
||||||
}}
|
}}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import BookIndex from './BookIndex.jsx';
|
import BookIndex from './BookIndex';
|
||||||
|
|
||||||
const { title, logoText, pages } = window.__BOOK_DATA__;
|
const { title, logoText, pages } = window.__BOOK_DATA__;
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<BookIndex title={title} logoText={logoText} pages={pages} />
|
<BookIndex title={title} logoText={logoText} pages={pages} />
|
||||||
);
|
);
|
||||||
16
src/globals.d.ts
vendored
Normal file
16
src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
declare module "*.css";
|
||||||
|
|
||||||
|
interface BookPage {
|
||||||
|
n: number;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookData {
|
||||||
|
title: string;
|
||||||
|
logoText: string;
|
||||||
|
pages: BookPage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
__BOOK_DATA__: BookData;
|
||||||
|
}
|
||||||
@@ -2,4 +2,4 @@ import React from 'react';
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import CharacterSheet from './CharacterSheet';
|
import CharacterSheet from './CharacterSheet';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(<CharacterSheet />);
|
createRoot(document.getElementById('root')!).render(<CharacterSheet />);
|
||||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -44,8 +44,8 @@ module.exports = (env, argv) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
entry: {
|
entry: {
|
||||||
sheet: "./src/sheet-main.jsx",
|
sheet: "./src/sheet-main.tsx",
|
||||||
book: "./src/book.js",
|
book: "./src/book.tsx",
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: isProd ? "[name].[contenthash].js" : "[name].js",
|
filename: isProd ? "[name].[contenthash].js" : "[name].js",
|
||||||
@@ -56,11 +56,13 @@ module.exports = (env, argv) => {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.(js|jsx)$/,
|
test: /\.(js|jsx|ts|tsx)$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: {
|
use: {
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: { presets: ["@babel/preset-react"] },
|
options: {
|
||||||
|
presets: ["@babel/preset-react", "@babel/preset-typescript"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,7 +75,7 @@ module.exports = (env, argv) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".js", ".jsx"],
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
...(isProd
|
...(isProd
|
||||||
|
|||||||
Reference in New Issue
Block a user