mirror of
https://github.com/quantum5/qcal.git
synced 2025-04-24 01:32:02 -04:00
Proof-of-concept Julian calendar
This commit is contained in:
parent
fc5f9b91b7
commit
c307af7ca4
3
common/global-setup.js
Normal file
3
common/global-setup.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = async () => {
|
||||
process.env.TZ = 'UTC';
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
testEnvironment: 'jsdom',
|
||||
globalSetup: './global-setup.js',
|
||||
};
|
||||
|
|
653
common/package-lock.json
generated
653
common/package-lock.json
generated
|
@ -23,6 +23,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"ts-jest": "^29.1.0"
|
||||
}
|
||||
},
|
||||
|
@ -1130,6 +1131,15 @@
|
|||
"@testing-library/dom": ">=7.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
|
||||
|
@ -1218,6 +1228,17 @@
|
|||
"pretty-format": "^27.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsdom": {
|
||||
"version": "20.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
|
||||
"integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/tough-cookie": "*",
|
||||
"parse5": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.18.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.24.tgz",
|
||||
|
@ -1271,6 +1292,12 @@
|
|||
"@types/jest": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
|
||||
"integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
|
||||
|
@ -1286,6 +1313,55 @@
|
|||
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-globals": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
|
||||
"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.1.0",
|
||||
"acorn-walk": "^8.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
|
@ -1353,6 +1429,12 @@
|
|||
"deep-equal": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
|
||||
|
@ -1697,6 +1779,18 @@
|
|||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
@ -1728,11 +1822,49 @@
|
|||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="
|
||||
},
|
||||
"node_modules/cssom": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
||||
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cssstyle": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
|
||||
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssom": "~0.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/cssom": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
|
||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
|
||||
"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"abab": "^2.0.6",
|
||||
"whatwg-mimetype": "^3.0.0",
|
||||
"whatwg-url": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
@ -1750,6 +1882,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
|
||||
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dedent": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
||||
|
@ -1783,6 +1921,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
|
@ -1807,6 +1951,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-newline": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||
|
@ -1829,6 +1982,18 @@
|
|||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
||||
"integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.368",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.368.tgz",
|
||||
|
@ -1853,6 +2018,18 @@
|
|||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
|
@ -1898,6 +2075,28 @@
|
|||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/escodegen": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
|
||||
"integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^5.2.0",
|
||||
"esutils": "^2.0.2",
|
||||
"optionator": "^0.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"escodegen": "bin/escodegen.js",
|
||||
"esgenerate": "bin/esgenerate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
|
@ -1911,6 +2110,24 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/estraverse": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
|
@ -2045,6 +2262,12 @@
|
|||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fb-watchman": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
||||
|
@ -2087,6 +2310,20 @@
|
|||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
@ -2281,12 +2518,51 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@tootallnate/once": "2",
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
|
@ -2296,6 +2572,18 @@
|
|||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/import-local": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
|
||||
|
@ -2507,6 +2795,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||
|
@ -3049,6 +3343,33 @@
|
|||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jest-environment-jsdom": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz",
|
||||
"integrity": "sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^29.5.0",
|
||||
"@jest/fake-timers": "^29.5.0",
|
||||
"@jest/types": "^29.5.0",
|
||||
"@types/jsdom": "^20.0.0",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^29.5.0",
|
||||
"jest-util": "^29.5.0",
|
||||
"jsdom": "^20.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^2.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz",
|
||||
|
@ -3658,6 +3979,51 @@
|
|||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "20.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
|
||||
"integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"abab": "^2.0.6",
|
||||
"acorn": "^8.8.1",
|
||||
"acorn-globals": "^7.0.0",
|
||||
"cssom": "^0.5.0",
|
||||
"cssstyle": "^2.3.0",
|
||||
"data-urls": "^3.0.2",
|
||||
"decimal.js": "^10.4.2",
|
||||
"domexception": "^4.0.0",
|
||||
"escodegen": "^2.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
"html-encoding-sniffer": "^3.0.0",
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"nwsapi": "^2.2.2",
|
||||
"parse5": "^7.1.1",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^4.1.2",
|
||||
"w3c-xmlserializer": "^4.0.0",
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-encoding": "^2.0.0",
|
||||
"whatwg-mimetype": "^3.0.0",
|
||||
"whatwg-url": "^11.0.0",
|
||||
"ws": "^8.11.0",
|
||||
"xml-name-validator": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^2.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
|
@ -3706,6 +4072,19 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
|
@ -3812,6 +4191,27 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
|
@ -3886,6 +4286,12 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/nwsapi": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz",
|
||||
"integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
@ -3966,6 +4372,23 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"deep-is": "~0.1.3",
|
||||
"fast-levenshtein": "~2.0.6",
|
||||
"levn": "~0.3.0",
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2",
|
||||
"word-wrap": "~1.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
|
@ -4035,6 +4458,18 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
|
@ -4107,6 +4542,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
|
@ -4144,6 +4588,21 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz",
|
||||
|
@ -4160,6 +4619,12 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
||||
|
@ -4232,6 +4697,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
|
||||
|
@ -4279,6 +4750,24 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"xmlchars": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
|
||||
|
@ -4512,6 +5001,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||
|
@ -4553,6 +5048,33 @@
|
|||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
|
||||
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
|
||||
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest": {
|
||||
"version": "29.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz",
|
||||
|
@ -4629,6 +5151,18 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"prelude-ls": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
|
@ -4662,6 +5196,15 @@
|
|||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||
|
@ -4692,6 +5235,16 @@
|
|||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
|
||||
|
@ -4712,6 +5265,18 @@
|
|||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
|
||||
"integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"xml-name-validator": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/walker": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
||||
|
@ -4726,6 +5291,49 @@
|
|||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
|
||||
"integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg=="
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
|
||||
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
|
||||
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tr46": "^3.0.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -4789,6 +5397,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
@ -4825,6 +5442,42 @@
|
|||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"ts-jest": "^29.1.0"
|
||||
}
|
||||
}
|
||||
|
|
36
common/src/DayChanger.test.tsx
Normal file
36
common/src/DayChanger.test.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import {act, render} from '@testing-library/react';
|
||||
import {DayChanger} from './DayChanger';
|
||||
|
||||
describe('Timezones', () => {
|
||||
it('should always be UTC', () => {
|
||||
expect(new Date().getTimezoneOffset()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DayChanger', () => {
|
||||
beforeEach(() => jest.useFakeTimers());
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
it('switches dates', () => {
|
||||
const start = new Date('2023-04-22T17:16:00');
|
||||
const end = new Date('2023-04-23T00:00:00');
|
||||
const spy = jest
|
||||
.spyOn(global, 'Date')
|
||||
// @ts-ignore we are mocking the constructor but TypeScript thinks we are mocking the function
|
||||
.mockImplementation(() => start);
|
||||
|
||||
const onChange = jest.fn();
|
||||
render(<DayChanger onDateChange={onChange}/>);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(24_239_999);
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
|
||||
// @ts-ignore we are mocking the constructor but TypeScript thinks we are mocking the function
|
||||
spy.mockImplementationOnce(() => end);
|
||||
|
||||
jest.advanceTimersByTime(1);
|
||||
expect(onChange).toHaveBeenCalledWith(2460058);
|
||||
});
|
||||
});
|
||||
});
|
26
common/src/DayChanger.tsx
Normal file
26
common/src/DayChanger.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import {dateJDN} from './gregorian';
|
||||
|
||||
function dayMs(date: Date): number {
|
||||
return date.getHours() * 3_600_000 + date.getMinutes() * 60_000 + date.getSeconds() * 1000 + date.getMilliseconds();
|
||||
}
|
||||
|
||||
type DayChangerProps = {
|
||||
onDateChange: (jdn: number) => void,
|
||||
};
|
||||
|
||||
export function DayChanger({onDateChange}: DayChangerProps): null {
|
||||
const [jdn, setJDN] = React.useState(dateJDN(new Date()));
|
||||
|
||||
React.useEffect(() => {
|
||||
const now = new Date();
|
||||
const timer = setTimeout(() => {
|
||||
const jdn = dateJDN(new Date());
|
||||
setJDN(jdn);
|
||||
onDateChange(jdn);
|
||||
}, 86_400_000 - dayMs(now));
|
||||
return () => clearTimeout(timer);
|
||||
}, [jdn, onDateChange]);
|
||||
|
||||
return null;
|
||||
}
|
|
@ -42,9 +42,7 @@ export type FrenchDate = {
|
|||
day: FrenchDay,
|
||||
};
|
||||
|
||||
const monthNames: {
|
||||
[key in FrenchMonth]: string
|
||||
} = {
|
||||
const monthNames: { [key in FrenchMonth]: string } = {
|
||||
1: 'Vendémiaire',
|
||||
2: 'Brumaire',
|
||||
3: 'Frimaire',
|
||||
|
@ -64,8 +62,8 @@ export const decadeNames = [
|
|||
'primidi', 'duodi', 'tridi', 'quartidi', 'quintidi', 'sextidi', 'septidi', 'octidi', 'nonidi', 'décadi',
|
||||
];
|
||||
|
||||
export const startJD = data.start_jd;
|
||||
export const startYear = data.start_year;
|
||||
export const frStartJD = data.start_jd;
|
||||
export const frStartYear = data.start_year;
|
||||
const leaps: Array<number> = [0];
|
||||
|
||||
let leapFromStart = 0;
|
||||
|
@ -75,23 +73,23 @@ data.leap.forEach(leap => {
|
|||
|
||||
});
|
||||
|
||||
export const endYear = startYear + leaps.length - 1;
|
||||
export const frEndYear = frStartYear + leaps.length - 1;
|
||||
|
||||
export function frSupportedYear(year: number): boolean {
|
||||
return startYear <= year && year <= endYear;
|
||||
return frStartYear <= year && year <= frEndYear;
|
||||
}
|
||||
|
||||
export function frJDN(year: number, month: FrenchMonth, day: FrenchDay): number {
|
||||
const dy = year - startYear;
|
||||
const dy = year - frStartYear;
|
||||
const dd = month * 30 + day - 31;
|
||||
return startJD + 365 * dy + leaps[dy] + dd;
|
||||
return frStartJD + 365 * dy + leaps[dy] + dd;
|
||||
}
|
||||
|
||||
export function frIsLeap(year: number): boolean {
|
||||
return !!data.leap[year - startYear];
|
||||
return !!data.leap[year - frStartYear];
|
||||
}
|
||||
|
||||
export const endJD = frJDN(endYear, 13, frIsLeap(endYear) ? 6 : 5);
|
||||
export const frEndJD = frJDN(frEndYear, 13, frIsLeap(frEndYear) ? 6 : 5);
|
||||
|
||||
export function jdnFrench(jdn: number): FrenchDate {
|
||||
let lo = 0;
|
||||
|
@ -99,18 +97,18 @@ export function jdnFrench(jdn: number): FrenchDate {
|
|||
|
||||
while (lo + 1 < hi) {
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
if (startJD + 365 * mid + leaps[mid] <= jdn)
|
||||
if (frStartJD + 365 * mid + leaps[mid] <= jdn)
|
||||
lo = mid;
|
||||
else
|
||||
hi = mid;
|
||||
}
|
||||
|
||||
const dd = jdn - (startJD + 365 * lo + leaps[lo]);
|
||||
const dd = jdn - (frStartJD + 365 * lo + leaps[lo]);
|
||||
return {
|
||||
year: startYear + lo,
|
||||
year: frStartYear + lo,
|
||||
month: Math.floor(dd / 30) + 1 as FrenchMonth,
|
||||
day: dd % 30 + 1 as FrenchDay,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function monthName(month: FrenchMonth): string {
|
||||
|
@ -147,5 +145,9 @@ export function dateRuralName(month: FrenchMonth, day: FrenchDay): { name: strin
|
|||
return {name, title};
|
||||
}
|
||||
|
||||
export const startGregorian = jdnGregorian(startJD);
|
||||
export const endGregorian = jdnGregorian(endJD);
|
||||
export const startGregorian = jdnGregorian(frStartJD);
|
||||
export const endGregorian = jdnGregorian(frEndJD);
|
||||
|
||||
export function frDateFormat({year, month, day}: { year: number, month: FrenchMonth, day: FrenchDay }): string {
|
||||
return `${dateName(month, day)} ${year}`;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {gregorianJDN, jdnDate, jdnGregorian, JulianDay, JulianMonth} from './gregorian';
|
||||
import {gregorianJDN, jdnDate, jdnGregorian, JulianDay, JulianMonth, gregorianMonthDays} from './gregorian';
|
||||
|
||||
describe('gregorianJDN', () => {
|
||||
it('works', () => {
|
||||
|
@ -67,3 +67,42 @@ describe('jdnGregorian', () => {
|
|||
checkJDN(0, -4712, 1, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('monthLength', () => {
|
||||
it('works for normal months', () => {
|
||||
expect(gregorianMonthDays(2023, 1)).toEqual(31); // January
|
||||
expect(gregorianMonthDays(2023, 3)).toEqual(31); // March
|
||||
expect(gregorianMonthDays(2023, 4)).toEqual(30); // April
|
||||
expect(gregorianMonthDays(2023, 5)).toEqual(31); // May
|
||||
expect(gregorianMonthDays(2023, 6)).toEqual(30); // June
|
||||
expect(gregorianMonthDays(2023, 7)).toEqual(31); // July
|
||||
expect(gregorianMonthDays(2023, 8)).toEqual(31); // August
|
||||
expect(gregorianMonthDays(2023, 9)).toEqual(30); // September
|
||||
expect(gregorianMonthDays(2023, 10)).toEqual(31); // October
|
||||
expect(gregorianMonthDays(2023, 11)).toEqual(30); // November
|
||||
expect(gregorianMonthDays(2023, 12)).toEqual(31); // December
|
||||
});
|
||||
|
||||
it('handles Gregorian leap years correctly', () => {
|
||||
// Leap year: divisible by 400
|
||||
expect(gregorianMonthDays(1600, 2)).toEqual(29);
|
||||
expect(gregorianMonthDays(2000, 2)).toEqual(29);
|
||||
expect(gregorianMonthDays(2400, 2)).toEqual(29);
|
||||
|
||||
// Not a leap year: divisible by 100 but not by 400
|
||||
expect(gregorianMonthDays(1700, 2)).toEqual(28);
|
||||
expect(gregorianMonthDays(1800, 2)).toEqual(28);
|
||||
expect(gregorianMonthDays(1900, 2)).toEqual(28);
|
||||
expect(gregorianMonthDays(2100, 2)).toEqual(28);
|
||||
|
||||
// Leap year: divisible by 4 but not by 100
|
||||
expect(gregorianMonthDays(2004, 2)).toEqual(29);
|
||||
expect(gregorianMonthDays(2008, 2)).toEqual(29);
|
||||
expect(gregorianMonthDays(2012, 2)).toEqual(29);
|
||||
expect(gregorianMonthDays(2016, 2)).toEqual(29);
|
||||
|
||||
expect(gregorianMonthDays(2001, 2)).toEqual(28);
|
||||
expect(gregorianMonthDays(2002, 2)).toEqual(28);
|
||||
expect(gregorianMonthDays(2003, 2)).toEqual(28);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,6 +34,25 @@ export type JulianDay =
|
|||
|
||||
export type JulianDate = [number, JulianMonth, JulianDay];
|
||||
|
||||
const monthNames: { [key in JulianMonth]: string } = {
|
||||
1: 'January',
|
||||
2: 'February',
|
||||
3: 'March',
|
||||
4: 'April',
|
||||
5: 'May',
|
||||
6: 'June',
|
||||
7: 'July',
|
||||
8: 'August',
|
||||
9: 'September',
|
||||
10: 'October',
|
||||
11: 'November',
|
||||
12: 'December',
|
||||
};
|
||||
|
||||
export const weekdayNames = [
|
||||
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
|
||||
];
|
||||
|
||||
export function gregorianJDN(year: number, month: number, day: number, julian_before?: number): number {
|
||||
const g = year + 4716 - (month <= 2 ? 1 : 0);
|
||||
const f = (month + 9) % 12;
|
||||
|
@ -67,3 +86,36 @@ export function jdnDate(jdn: number, julian_before?: number): Date {
|
|||
const [year, month, day] = jdnGregorian(jdn, julian_before);
|
||||
return new Date(year, month - 1, day);
|
||||
}
|
||||
|
||||
export function monthName(month: JulianMonth): string {
|
||||
return monthNames[month];
|
||||
}
|
||||
|
||||
export function gregorianMonthDays(year: number, month: JulianMonth, julian = false): 28 | 29 | 30 | 31 {
|
||||
switch (month) {
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
case 7:
|
||||
case 8:
|
||||
case 10:
|
||||
case 12:
|
||||
return 31;
|
||||
case 4:
|
||||
case 6:
|
||||
case 9:
|
||||
case 11:
|
||||
return 30;
|
||||
case 2:
|
||||
if (year % 4 !== 0)
|
||||
return 28;
|
||||
if (!julian && (year % 100 === 0 && year % 400 !== 0))
|
||||
return 28;
|
||||
return 29;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatJG([year, month, day]: [number, JulianMonth, JulianDay]): string {
|
||||
const m = monthNames[month].substring(0, 3);
|
||||
return `${m} ${day} ${year}`;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {jdnJulian, julianJDN} from './julian';
|
||||
import {jdnJulian, julianJDN, julianMonthDays} from './julian';
|
||||
|
||||
describe('gregorianJDN', () => {
|
||||
it('works', () => {
|
||||
|
@ -23,3 +23,42 @@ describe('jdnGregorian', () => {
|
|||
expect(jdnJulian(0)).toEqual([-4712, 1, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('monthLength', () => {
|
||||
it('works for normal months', () => {
|
||||
expect(julianMonthDays(2023, 1)).toEqual(31); // January
|
||||
expect(julianMonthDays(2023, 3)).toEqual(31); // March
|
||||
expect(julianMonthDays(2023, 4)).toEqual(30); // April
|
||||
expect(julianMonthDays(2023, 5)).toEqual(31); // May
|
||||
expect(julianMonthDays(2023, 6)).toEqual(30); // June
|
||||
expect(julianMonthDays(2023, 7)).toEqual(31); // July
|
||||
expect(julianMonthDays(2023, 8)).toEqual(31); // August
|
||||
expect(julianMonthDays(2023, 9)).toEqual(30); // September
|
||||
expect(julianMonthDays(2023, 10)).toEqual(31); // October
|
||||
expect(julianMonthDays(2023, 11)).toEqual(30); // November
|
||||
expect(julianMonthDays(2023, 12)).toEqual(31); // December
|
||||
});
|
||||
|
||||
it('handles Gregorian leap years correctly', () => {
|
||||
// Leap year: divisible by 400
|
||||
expect(julianMonthDays(1600, 2)).toEqual(29);
|
||||
expect(julianMonthDays(2000, 2)).toEqual(29);
|
||||
expect(julianMonthDays(2400, 2)).toEqual(29);
|
||||
|
||||
// Leap year in Julian calendar: divisible by 100 but not by 400
|
||||
expect(julianMonthDays(1700, 2)).toEqual(29);
|
||||
expect(julianMonthDays(1800, 2)).toEqual(29);
|
||||
expect(julianMonthDays(1900, 2)).toEqual(29);
|
||||
expect(julianMonthDays(2100, 2)).toEqual(29);
|
||||
|
||||
// Leap year: divisible by 4 but not by 100
|
||||
expect(julianMonthDays(2004, 2)).toEqual(29);
|
||||
expect(julianMonthDays(2008, 2)).toEqual(29);
|
||||
expect(julianMonthDays(2012, 2)).toEqual(29);
|
||||
expect(julianMonthDays(2016, 2)).toEqual(29);
|
||||
|
||||
expect(julianMonthDays(2001, 2)).toEqual(28);
|
||||
expect(julianMonthDays(2002, 2)).toEqual(28);
|
||||
expect(julianMonthDays(2003, 2)).toEqual(28);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {gregorianJDN, jdnGregorian, JulianDate} from './gregorian';
|
||||
import {gregorianJDN, jdnGregorian, gregorianMonthDays, JulianDate, JulianMonth} from './gregorian';
|
||||
|
||||
export function julianJDN(year: number, month: number, day: number): number {
|
||||
return gregorianJDN(year, month, day, Infinity);
|
||||
|
@ -7,3 +7,7 @@ export function julianJDN(year: number, month: number, day: number): number {
|
|||
export function jdnJulian(jdn: number): JulianDate {
|
||||
return jdnGregorian(jdn, Infinity);
|
||||
}
|
||||
|
||||
export function julianMonthDays(year: number, month: JulianMonth): 28 | 29 | 30 | 31 {
|
||||
return gregorianMonthDays(year, month, true);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"@types/node": "^16.11.24",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap": "~5.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "5.0.0",
|
||||
|
|
|
@ -2,12 +2,12 @@ import React, {FormEvent} from 'react';
|
|||
import {Calendar} from './Calendar';
|
||||
import {
|
||||
endGregorian,
|
||||
endJD,
|
||||
frEndJD,
|
||||
FrenchMonth,
|
||||
frSupportedYear,
|
||||
jdnFrench,
|
||||
startGregorian,
|
||||
startJD,
|
||||
frStartJD,
|
||||
} from '@common/french';
|
||||
import {dateJDN, gregorianJDN} from '@common/gregorian';
|
||||
import {TimeOfDay} from './TimeOfDay';
|
||||
|
@ -99,7 +99,7 @@ class App extends React.Component<{}, AppState> {
|
|||
return;
|
||||
|
||||
const jdn = gregorianJDN(+this.state.goYear, +this.state.goMonth, +this.state.goDay);
|
||||
const {year, month} = jdnFrench(Math.min(Math.max(startJD, jdn), endJD));
|
||||
const {year, month} = jdnFrench(Math.min(Math.max(frStartJD, jdn), frEndJD));
|
||||
this.setState({year, month});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@ import {
|
|||
dateName,
|
||||
dateRuralName,
|
||||
decadeNames,
|
||||
endYear,
|
||||
frEndYear,
|
||||
FrenchDay,
|
||||
FrenchMonth,
|
||||
frIsLeap,
|
||||
frJDN,
|
||||
jdnFrench,
|
||||
monthName,
|
||||
startYear,
|
||||
frStartYear,
|
||||
} from '@common/french';
|
||||
import {jdnDate} from '@common/gregorian';
|
||||
import {jdnLongCount} from '@common/longCount';
|
||||
|
@ -123,11 +123,11 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
|
|||
month -= 13;
|
||||
}
|
||||
|
||||
if (year < startYear) {
|
||||
year = startYear;
|
||||
if (year < frStartYear) {
|
||||
year = frStartYear;
|
||||
month = 1;
|
||||
} else if (year > endYear) {
|
||||
year = endYear;
|
||||
} else if (year > frEndYear) {
|
||||
year = frEndYear;
|
||||
month = 13;
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,7 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
|
|||
})
|
||||
}</select>
|
||||
<input type="number" className="Calendar-year-input form-control" value={this.state.yearStr}
|
||||
onChange={this.yearChange} min={startYear} max={endYear}/>
|
||||
onChange={this.yearChange} min={frStartYear} max={frEndYear}/>
|
||||
<button type="button" className="form-control btn btn-primary Calendar-today-button"
|
||||
onClick={this.goToToday}>Today
|
||||
</button>
|
||||
|
|
File diff suppressed because one or more lines are too long
5
jcal/config-overrides.js
Normal file
5
jcal/config-overrides.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const {aliasWebpack, aliasJest} = require('react-app-alias-ex');
|
||||
|
||||
const options = {};
|
||||
module.exports = aliasWebpack(options);
|
||||
module.exports.jest = aliasJest(options);
|
15587
jcal/package-lock.json
generated
Normal file
15587
jcal/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
51
jcal/package.json
Normal file
51
jcal/package.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "jcal",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.2",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/node": "^16.11.24",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"bootstrap": "~5.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"sass": "^1.49.7",
|
||||
"sass-loader": "^12.4.0",
|
||||
"typescript": "^4.5.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootstrap": "^5.1.9",
|
||||
"react-app-alias-ex": "^2.1.0",
|
||||
"react-app-rewired": "^2.2.1"
|
||||
}
|
||||
}
|
BIN
jcal/public/favicon.ico
Normal file
BIN
jcal/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
138
jcal/public/index.html
Normal file
138
jcal/public/index.html
Normal file
|
@ -0,0 +1,138 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="theme-color" content="#ff0000"/>
|
||||
<meta name="description"
|
||||
content="An interactive French Republican Calendar (a.k.a. French Revolutionary Calendar) that uses the original equinox method and never drifts out of sync with the seasons, along with revolutionary decimal time."/>
|
||||
<meta property="og:title" content="French Republican Calendar"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:url" content="https://frcal.qt.ax/"/>
|
||||
<meta property="og:description"
|
||||
content="An interactive French Republican Calendar (a.k.a. French Revolutionary Calendar) that uses the original equinox method and never drifts out of sync with the seasons, along with revolutionary decimal time."/>
|
||||
<meta property="og:image" content="%PUBLIC_URL%/logo512.png"/>
|
||||
<meta property="og:image:type" content="image/png"/>
|
||||
<meta property="og:image:width" content="512"/>
|
||||
<meta property="og:image:height" content="512"/>
|
||||
<meta property="og:image:alt" content="A calendar icon that displays the date 18 Brumaire."/>
|
||||
<link rel="canonical" href="%PUBLIC_URL%/"/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png"/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
||||
<title>Julian Calendar (extended indefinitely to the future)</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-light">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="#">Julian Calendar</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav-anchors"
|
||||
aria-controls="nav-anchors" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="nav-anchors">
|
||||
<ul class="nav navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#explanation">Explanation</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://frcal.qt.ax">French Republican Calendar</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div class="main">
|
||||
<h2 id="explanation">Explanation</h2>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">What is this?</h4>
|
||||
<p class="lead">The <a href="https://en.wikipedia.org/wiki/Julian_calendar">Julian
|
||||
calendar</a> is a calendar introduced by Julius Caesar in 46 BC. A reformed version, the <a
|
||||
href="https://en.wikipedia.org/wiki/Gregorian_calendar">Gregorian
|
||||
calendar</a>, forms the basis of civil calendar in most countries.</p>
|
||||
<p>Julius Caesar (with aid of Sosigenes of Alexandria) introduced this calendar to replace the earlier Roman
|
||||
calendar, which required manual addition of leap months to keep it in synchrony with the
|
||||
seasons.Unfortunately, it had leap days every four years without question, resulting in the average year
|
||||
having 365.25 days. However, the actual tropical year is roughly 365.2422 days, resulting in the Julian
|
||||
calendar gaining a day every 129 years. This means that seasons drift, starting earlier and earlier in
|
||||
the year.</p>
|
||||
<p>In 1582, Pope Gregory XIII decided to fix this drift by reducing the number of leap days, motivated by a
|
||||
desire to keep the March equinox on March 21st, since that value was hardcoded in the calculation for
|
||||
the date of Easter. To achieve this, he made years divisible by 100 but not by 400 non-leap years,
|
||||
resulting in 97 leap years every 400 years, resulting in the Gregorian calendar. To bring the equinox
|
||||
back in alignment, October 5th to October 14th in 1582 were deleted. The result is the Gregorian
|
||||
calendar.</p>
|
||||
<p>This website extends the Julian calendar indefinitely into the future for reference reasons. Note that in
|
||||
the 20th and 21st centuries, the Julian calendar is 13 days behind the Gregorian calendar.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">How are BCE years handled?</h4>
|
||||
<p class="lead">The astronomical convention is used, i.e. year 0 is 1 BCE, year -1 is 2 BCE, etc.</p>
|
||||
<p>Due to the way the common era (a.k.a. Anno Domini) year numbering system works, the year 1 BCE is
|
||||
followed directly by 1 CE with no year zero. This makes math hard. As such, the astronomical convention
|
||||
of making 1 BCE year 0 and extending this into the past was used.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">What is the JD (Julian day number) value?</h4>
|
||||
<p>The integer <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day (JD) number</a> is the
|
||||
continuous count of days since the beginning of the Julian period, which is very useful in astronomy and
|
||||
in software for calculating durations without worrying about weird calendar issues. In this respect,
|
||||
it's very similar to Unix time.</p>
|
||||
<p>JD 0 is assigned to the date Monday, January 1, 4713 BCE (Julian) or November 24, 4714 BCE (Gregorian).
|
||||
More specifically, if fractional JDs are considered, then the integer value specifically refers to the
|
||||
Universal Time noon on that date.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">What is the LC (Mesoamerican Long Count) date?</h4>
|
||||
<p>The five (or more) numbers separated by dots is the corresponding
|
||||
<a href="https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar">Mesoamerican Long Count
|
||||
calendar</a> date. This is commonly known as the “Mayan calendar.” This calendar is not
|
||||
available for dates before August 11, 3114 BCE (Gregorian) or September 6, 3114 BCE (Julian).</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">What is the FR (French Republican calendar) date?</h4>
|
||||
<p>The <a href="https://en.wikipedia.org/wiki/French_Republican_calendar">French Republican
|
||||
calendar</a> was a calendar created and implemented during the French Revolution. It is also frequently
|
||||
referred to as the <em>French Revolutionary Calendar</em>, but this is a misnomer:
|
||||
year 1 of the calendar started on 22 September 1792, the day after the
|
||||
<a href="https://en.wikipedia.org/wiki/Proclamation_of_the_abolition_of_the_monarchy">abolition of the
|
||||
monarchy</a> and the founding of the <a href="https://en.wikipedia.org/wiki/French_First_Republic">French
|
||||
First Republic</a>.</p>
|
||||
<p>Specifically, the variant used here is <a href="https://frcal.qt.ax/">the one I computed from
|
||||
astronomy</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p class="text-muted">Copyright © 2022<%= new Date().getFullYear() > 2022 ? `–${new Date().getFullYear()}`
|
||||
: '' %>
|
||||
<a href="https://quantum5.ca">Quantum</a>.
|
||||
Licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">GNU AGPLv3</a>.
|
||||
Source code available on <a href="https://github.com/quantum5/frcal">GitHub</a>.<br>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
(function(j,u,l,i,a,n,c){j['GoogleAnalyticsObject']=a;j[a]=j[a]||function(){
|
||||
(j[a].q=j[a].q||[]).push(arguments)},j[a].l=1*new Date();n=u.createElement(l),
|
||||
c=u.getElementsByTagName(l)[0];n.async=1;n.src=i;c.parentNode.insertBefore(n,c)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-102581070-4', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
70
jcal/public/logo.svg
Normal file
70
jcal/public/logo.svg
Normal file
|
@ -0,0 +1,70 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" viewBox="0 0 87.6 98.5">
|
||||
<defs>
|
||||
<linearGradient id="a">
|
||||
<stop offset="0" stop-color="#d0bcba"/>
|
||||
<stop offset="1" stop-color="#fefdfd"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c">
|
||||
<stop offset="0" stop-color="#d0bcba"/>
|
||||
<stop offset="1" stop-color="#6c6c6c"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b">
|
||||
<stop offset="0" stop-color="#978888"/>
|
||||
<stop offset="1" stop-color="#fbfcfc"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="d" x1="-33.8" x2="14.2" y1="36.5" y2="50.5" gradientTransform="translate(178.8 49.4)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#be9e70"/>
|
||||
<stop offset="1" stop-color="#694c30"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="e" x1="195.9" x2="196.2" y1="89" y2="70.7" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#e40102"/>
|
||||
<stop offset="1" stop-color="#bb7874"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="f" x1="195.9" x2="195.9" y1="73" y2="103.6" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#7c1f16"/>
|
||||
<stop offset="1" stop-color="#7c1f16" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="g" x1="197.6" x2="196.7" y1="81.1" y2="88.7" gradientTransform="translate(0 .2)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#aa090a"/>
|
||||
<stop offset="1" stop-color="#e50102"/>
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#a" id="i" x1="281.2" x2="279.6" y1="94" y2="92.5" gradientTransform="translate(-34.4 9.2)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#a" id="j" x1="213.3" x2="223.5" y1="113.9" y2="122.7" gradientTransform="translate(29.5 -14)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#b" id="k" x1="221.6" x2="219.2" y1="35" y2="35" gradientTransform="matrix(1.078 0 0 1.0746 -55.1 33.9)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#c" id="l" x1="250.1" x2="250.1" y1="38.6" y2="26.5" gradientTransform="translate(-68 39)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#b" id="m" x1="221.6" x2="219.2" y1="35" y2="35" gradientTransform="matrix(1.078 0 0 1.0746 -47.6 33.9)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#c" id="n" x1="250.1" x2="250.1" y1="38.6" y2="26.5" gradientTransform="translate(-60.5 39)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#b" id="o" x1="221.6" x2="219.2" y1="35" y2="35" gradientTransform="matrix(1.078 0 0 1.0746 -40 33.9)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#c" id="p" x1="250.1" x2="250.1" y1="38.6" y2="26.5" gradientTransform="translate(-53 39)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#b" id="q" x1="221.6" x2="219.2" y1="35" y2="35" gradientTransform="matrix(1.078 0 0 1.0746 -32.6 33.9)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#c" id="r" x1="250.1" x2="250.1" y1="38.6" y2="26.5" gradientTransform="translate(-45.5 39)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#b" id="s" x1="221.6" x2="219.2" y1="35" y2="35" gradientTransform="matrix(1.078 0 0 1.0746 -25 33.9)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#c" id="t" x1="250.1" x2="250.1" y1="38.6" y2="26.5" gradientTransform="translate(-38 39)" gradientUnits="userSpaceOnUse"/>
|
||||
<radialGradient id="h" cx="196.9" cy="132.3" r="21" gradientTransform="matrix(0 .98878 -1.0116 0 343.8 -116.1)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#fffefe"/>
|
||||
<stop offset="1" stop-color="#d5d7da"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g transform="matrix(1.78746 0 0 1.85973 -259.2 -121.6)">
|
||||
<rect width="48" height="48" x="146" y="70.4" fill="#694c30" rx="1.6" ry="1.5"/>
|
||||
<rect width="48" height="48" x="145" y="69.4" fill="url(#d)" rx="1.6" ry="1.5"/>
|
||||
</g>
|
||||
<path fill="url(#e)" stroke="url(#f)" stroke-linecap="round" stroke-width=".6" d="M177 73.3h41.4v15.4H177z" transform="matrix(1.8247 0 0 1.82257 -316.8 -121.9)"/>
|
||||
<path fill="url(#g)" d="M177 74v14.5h41.2V86c-10.5-.1-14-2.4-18.8-4.4-4.3-1.8-13.5-.1-17.8-1.3-3.2-1.4-3-2.7-4.5-6.2z" transform="matrix(1.8247 0 0 1.82257 -316.8 -121.9)"/>
|
||||
<path fill="url(#h)" stroke="#afa497" stroke-linecap="round" stroke-width=".6" d="M206.2 75.1h41.4v29.4h-41.4z" transform="translate(-370.2 -96.5) scale(1.8247)"/>
|
||||
<path fill="none" stroke="#c60002" stroke-opacity=".1" d="M227.4 90.9h41.2m-41.2 4h41.2M227.4 115h41.2m-41.2-4h41.2m-41.2-4.1h41.2m-41.2-4h41.2m-41.2-4h41.2" transform="matrix(1.8247 0 0 1.82257 -408.3 -119.2)"/>
|
||||
<path fill="url(#i)" fill-rule="evenodd" stroke="url(#j)" stroke-width=".5" d="M247.5 97.6a16 16 0 0 1-5.7 6.7c2.1.3 4-.5 5-1 .4-1.6.7-4 .7-5.7z" opacity=".5" transform="translate(-370.2 -96.5) scale(1.8247)"/>
|
||||
<g stroke-linecap="round" stroke-width=".7" transform="matrix(1.8247 0 0 1.82257 -315.8 -119.3)">
|
||||
<rect width="2.9" height="11.4" x="180.6" y="65.8" fill="url(#k)" stroke="url(#l)" rx="1.4" ry="2.2"/>
|
||||
<rect width="2.9" height="11.4" x="188.1" y="65.8" fill="url(#m)" stroke="url(#n)" rx="1.4" ry="2.2"/>
|
||||
<rect width="2.9" height="11.4" x="195.7" y="65.8" fill="url(#o)" stroke="url(#p)" rx="1.4" ry="2.2"/>
|
||||
<rect width="2.9" height="11.4" x="203.2" y="65.8" fill="url(#q)" stroke="url(#r)" rx="1.4" ry="2.2"/>
|
||||
<rect width="2.9" height="11.4" x="210.7" y="65.8" fill="url(#s)" stroke="url(#t)" rx="1.4" ry="2.2"/>
|
||||
</g>
|
||||
<g style="line-height:1.25" word-spacing="0">
|
||||
<path fill="#fff" d="M15.2 24.5h4.2l2.8 6.7 3-6.7h4V37h-3v-9.2l-3 6.8h-2l-2.9-6.8V37h-3zm21.4 8.3q-1 0-1.4.4-.5.3-.5.9 0 .6.4 1 .3.2 1 .2.9 0 1.5-.6t.6-1.5v-.4zm4.6-1.1V37h-3v-1.4q-.7.9-1.4 1.3-.8.3-1.9.3-1.4 0-2.4-.8-.9-.9-.9-2.2 0-1.7 1.2-2.5 1.1-.8 3.6-.8h1.8v-.2q0-.7-.6-1-.6-.4-1.8-.4-1 0-1.8.2t-1.5.6v-2.3l2-.4 1.9-.1q2.5 0 3.7 1 1.1 1 1.1 3.3zm9.8-1.5-.7-.3h-.8q-1.2 0-1.8.7-.6.7-.6 2.1v4.4h-3v-9.5h3v1.6q.5-1 1.3-1.4.8-.4 1.8-.4h.9zm9.2-2.2v2.4l-1.2-.6q-.6-.2-1.3-.2-1.3 0-2 .7t-.7 2q0 1.4.7 2.1.7.8 2 .8l1.4-.2q.6-.2 1.1-.7v2.5l-1.4.4-1.5.1q-2.5 0-4-1.3-1.4-1.3-1.4-3.6 0-2.4 1.4-3.7 1.5-1.3 4-1.3l1.5.1 1.4.4zm12.2 3.3v5.8h-3v-4.4l-.1-1.7q0-.5-.2-.7l-.5-.5-.7-.1q-1 0-1.5.7t-.5 2v4.7h-3V23.9h3V29q.7-.8 1.4-1.2.8-.4 1.7-.4 1.7 0 2.5 1 .9 1 .9 3z" aria-label="March"/>
|
||||
</g>
|
||||
<g style="line-height:1.25" word-spacing="0">
|
||||
<path d="M24.6 74.7h5.3V59.6l-5.4 1.1v-4l5.4-1.2h5.7v19.2h5.3v4.1H24.6zm21.9-19.2h15V60H51.3v3.6l1.4-.2 1.4-.1q4.3 0 6.7 2 2.3 2.2 2.3 6t-2.6 6q-2.5 2-7.1 2-2 0-4-.3-1.9-.4-3.8-1.2V73q1.9 1.1 3.6 1.7 1.7.5 3.2.5 2.2 0 3.5-1 1.2-1.1 1.2-3 0-1.8-1.2-2.9-1.3-1-3.5-1-1.3 0-2.7.3-1.5.3-3.2 1z" aria-label="15"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.9 KiB |
BIN
jcal/public/logo192.png
Normal file
BIN
jcal/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
jcal/public/logo512.png
Normal file
BIN
jcal/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
25
jcal/public/manifest.json
Normal file
25
jcal/public/manifest.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "French Republican Calendar",
|
||||
"name": "French Republican Calendar",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "128x128 64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#ff0000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
jcal/public/robots.txt
Normal file
3
jcal/public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
143
jcal/src/App.tsx
Normal file
143
jcal/src/App.tsx
Normal file
|
@ -0,0 +1,143 @@
|
|||
import React, {FormEvent} from 'react';
|
||||
import {Calendar} from './Calendar';
|
||||
import {frEndJD, frStartJD} from '@common/french';
|
||||
import {dateJDN, gregorianJDN, JulianMonth} from '@common/gregorian';
|
||||
import {DayChanger} from '@common/DayChanger';
|
||||
import {jdnJulian} from '@common/julian';
|
||||
|
||||
// Not real limitations other than JS number precision.
|
||||
const START_YEAR = -10_000_000_000_000;
|
||||
const END_YEAR = 10_000_000_000_000;
|
||||
|
||||
type YearMonth = {
|
||||
year: number;
|
||||
month: JulianMonth;
|
||||
}
|
||||
|
||||
function parseURL(): YearMonth | null {
|
||||
const match = /\/(-?\d+)\/(\d+)/.exec(window.location.pathname);
|
||||
if (!match)
|
||||
return null;
|
||||
|
||||
const month = +match[2];
|
||||
const year = +match[1];
|
||||
if (month < 1 || month > 23)
|
||||
return null;
|
||||
return {year: year, month: month as JulianMonth};
|
||||
}
|
||||
|
||||
type AppState = YearMonth & {
|
||||
todayJDN: number,
|
||||
goYear: string,
|
||||
goMonth: string,
|
||||
goDay: string,
|
||||
};
|
||||
|
||||
class App extends React.Component<{}, AppState> {
|
||||
state: AppState;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
const today = new Date();
|
||||
const todayJDN = dateJDN(today);
|
||||
const [year, month] = jdnJulian(todayJDN);
|
||||
|
||||
this.state = {
|
||||
...(parseURL() || {year, month}),
|
||||
todayJDN,
|
||||
goYear: today.getFullYear().toString(),
|
||||
goMonth: (today.getMonth() + 1).toString(),
|
||||
goDay: today.getDate().toString(),
|
||||
};
|
||||
this.updateStateFromURL = this.updateStateFromURL.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('popstate', this.updateStateFromURL);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('popstate', this.updateStateFromURL);
|
||||
}
|
||||
|
||||
private updateStateFromURL(event: PopStateEvent) {
|
||||
this.setState(event.state);
|
||||
}
|
||||
|
||||
private updateURL() {
|
||||
const {year, month} = this.state;
|
||||
const path = `/${year}/${month}`;
|
||||
if (path !== window.location.pathname) {
|
||||
window.history.pushState({year, month}, '', path);
|
||||
}
|
||||
}
|
||||
|
||||
changeField(field: keyof AppState, event: any) {
|
||||
this.setState({[field]: event.target.value});
|
||||
}
|
||||
|
||||
validYear() {
|
||||
return /^-?\d+$/.test(this.state.goYear) && START_YEAR <= +this.state.goYear && +this.state.goYear <= END_YEAR;
|
||||
}
|
||||
|
||||
validMonth() {
|
||||
return /^\d+$/.test(this.state.goMonth) && 1 <= +this.state.goMonth && +this.state.goMonth <= 12;
|
||||
}
|
||||
|
||||
validDay() {
|
||||
return /^\d+$/.test(this.state.goDay) && 1 <= +this.state.goDay && +this.state.goDay <= 31;
|
||||
}
|
||||
|
||||
goToGregorian(event: FormEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.validYear() || !this.validMonth() || !this.validDay())
|
||||
return;
|
||||
|
||||
const jdn = gregorianJDN(+this.state.goYear, +this.state.goMonth, +this.state.goDay);
|
||||
const [year, month] = jdnJulian(Math.min(Math.max(frStartJD, jdn), frEndJD));
|
||||
this.setState({year, month});
|
||||
}
|
||||
|
||||
setState(state: any, callback?: () => void) {
|
||||
super.setState(state, () => {
|
||||
this.updateURL();
|
||||
callback?.();
|
||||
});
|
||||
}
|
||||
|
||||
onDateChange = (todayJDN: number) => {
|
||||
this.setState({todayJDN});
|
||||
};
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<Calendar
|
||||
year={this.state.year} month={this.state.month} todayJDN={this.state.todayJDN}
|
||||
onSwitch={(year, month) => {
|
||||
this.setState({year, month});
|
||||
}}/>
|
||||
|
||||
<DayChanger onDateChange={this.onDateChange}/>
|
||||
|
||||
<div className="navigate">
|
||||
<h4>Go to a date</h4>
|
||||
<form className="input-group" onSubmit={this.goToGregorian.bind(this)}>
|
||||
<span className="input-group-text">Gregorian<span className="hide-small"> Date</span></span>
|
||||
<input type="number" className={`form-control go-year ${this.validYear() ? '' : 'is-invalid'}`}
|
||||
onChange={this.changeField.bind(this, 'goYear')} value={this.state.goYear}
|
||||
min={START_YEAR} max={END_YEAR}/>
|
||||
<input type="number" className={`form-control go-month ${this.validMonth() ? '' : 'is-invalid'}`}
|
||||
onChange={this.changeField.bind(this, 'goMonth')} value={this.state.goMonth}
|
||||
min={1} max={12}/>
|
||||
<input type="number" className={`form-control go-day ${this.validDay() ? '' : 'is-invalid'}`}
|
||||
onChange={this.changeField.bind(this, 'goDay')} value={this.state.goDay}
|
||||
min={1} max={31}/>
|
||||
<button type="submit" className="form-control btn btn-primary go-button">Go</button>
|
||||
</form>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
164
jcal/src/Calendar.scss
Normal file
164
jcal/src/Calendar.scss
Normal file
|
@ -0,0 +1,164 @@
|
|||
@import 'bootstrap/scss/functions';
|
||||
@import 'bootstrap/scss/variables';
|
||||
@import 'bootstrap/scss/mixins';
|
||||
@import 'bootstrap/scss/forms';
|
||||
@import 'bootstrap/scss/grid';
|
||||
@import 'bootstrap/scss/buttons';
|
||||
@import './consts';
|
||||
|
||||
.Calendar {
|
||||
@include make-container();
|
||||
}
|
||||
|
||||
.Calendar-head {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 2em;
|
||||
font-weight: 600;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: white;
|
||||
margin-top: -$spacer * 4;
|
||||
padding-top: $spacer * 4;
|
||||
|
||||
.btn {
|
||||
margin: 0 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.Calendar-prev, .Calendar-next {
|
||||
flex: 0 2.5em;
|
||||
}
|
||||
|
||||
.Calendar-prev {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.Calendar-month-name {
|
||||
flex: 1;
|
||||
max-width: 30rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Month-weekdayHead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Month-days {
|
||||
@include make-row($gutter: 0);
|
||||
|
||||
> * {
|
||||
@include make-col-ready();
|
||||
}
|
||||
}
|
||||
|
||||
.DayOuter {
|
||||
padding: $calendar-gutter;
|
||||
}
|
||||
|
||||
.DayFiller {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DayOuter, .WeekdayName {
|
||||
display: flex;
|
||||
@include make-col($size: 1, $columns: 2);
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.DayOuter, .WeekdayName {
|
||||
@include make-col($size: 1, $columns: 3);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.DayOuter, .WeekdayName {
|
||||
@include make-col($size: 1, $columns: 4);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
.DayOuter, .WeekdayName {
|
||||
@include make-col($size: 1, $columns: 5);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
.Month-weekdayHead {
|
||||
display: block;
|
||||
@include make-row($gutter: 0);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
margin-top: -$spacer * 7;
|
||||
padding-top: $spacer * 7;
|
||||
}
|
||||
|
||||
.DayOuter, .WeekdayName, .DayFiller {
|
||||
@include make-col($size: 1, $columns: 7);
|
||||
}
|
||||
|
||||
.Day-weekday {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DayFiller {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.Month {
|
||||
margin-left: -$calendar-gutter;
|
||||
margin-right: -$calendar-gutter;
|
||||
}
|
||||
|
||||
.Day, .DayFiller {
|
||||
padding: 0.5em;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.Day {
|
||||
flex: 1;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.DayFiller {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.WeekdayName {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.Day-name {
|
||||
font-size: 2em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.Day-weekday {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.Day-today {
|
||||
background: $gray-300;
|
||||
}
|
||||
|
||||
.Calendar-month-name.input-group {
|
||||
justify-content: center;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.Calendar-month-input {
|
||||
max-width: 12.5em;
|
||||
}
|
||||
|
||||
.Calendar-year-input {
|
||||
max-width: 6em;
|
||||
}
|
||||
|
||||
.Calendar-today-button {
|
||||
max-width: 5em;
|
||||
}
|
213
jcal/src/Calendar.tsx
Normal file
213
jcal/src/Calendar.tsx
Normal file
|
@ -0,0 +1,213 @@
|
|||
import React from 'react';
|
||||
import './Calendar.scss';
|
||||
import {formatJG, jdnGregorian, JulianDay, JulianMonth, monthName, weekdayNames} from '@common/gregorian';
|
||||
import {jdnLongCount} from '@common/longCount';
|
||||
import {jdnJulian, julianJDN, julianMonthDays} from '@common/julian';
|
||||
import {frDateFormat, frEndJD, frStartJD, jdnFrench} from '@common/french';
|
||||
|
||||
type MonthProps = {
|
||||
year: number;
|
||||
month: JulianMonth;
|
||||
};
|
||||
|
||||
type DateProps = MonthProps & {
|
||||
day: JulianDay;
|
||||
};
|
||||
|
||||
function WeekdayName({name}: { name: string }): JSX.Element {
|
||||
return <div className="WeekdayName">{name}</div>;
|
||||
}
|
||||
|
||||
function DayDetail({jdn}: { jdn: number }): JSX.Element {
|
||||
const lc = jdnLongCount(jdn);
|
||||
return <div className="DayDetail">
|
||||
<div className="DayDetail-jdn"><abbr title="Julian day number">JD</abbr> {jdn}</div>
|
||||
<div className="DayDetail-gregorian">
|
||||
<abbr title="Gregorian date">G.</abbr>{' '}
|
||||
{formatJG(jdnGregorian(jdn))}
|
||||
</div>
|
||||
{lc && <div className="DayDetail-lc">
|
||||
<abbr title="Mesoamerican long count date">LC</abbr>{' '}
|
||||
{lc.join('.\u200b')}
|
||||
</div>}
|
||||
{jdn >= frStartJD && jdn <= frEndJD && <div className="DayDetail-fr">
|
||||
<abbr title="French Republican Calendar">FR</abbr>{' '}
|
||||
{frDateFormat(jdnFrench(jdn))}
|
||||
</div>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function Day({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JSX.Element {
|
||||
const jdn = julianJDN(year, month, day);
|
||||
return <div className={`Day NormalDay ${jdn === todayJDN ? 'Day-today' : ''}`}>
|
||||
<div className="Day-name">{day}</div>
|
||||
<div className="Day-weekday">{weekdayNames[(day - 1) % 7]}</div>
|
||||
<DayDetail jdn={jdn}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function Month({year, month, todayJDN}: MonthProps & { todayJDN: number }): JSX.Element {
|
||||
const decadeHeads = weekdayNames.map((name, i) => <WeekdayName key={i} name={name}/>);
|
||||
const firstJDN = julianJDN(year, month, 1);
|
||||
const firstWeekday = (firstJDN + 1) % 7;
|
||||
const daysTotal = julianMonthDays(year, month);
|
||||
return <div className="Month">
|
||||
<div className="Month-weekdayHead">{decadeHeads}</div>
|
||||
<div className="Month-days">{
|
||||
Array.from(Array(6).keys()).flatMap(i => {
|
||||
if (i * 7 - firstWeekday + 1 > daysTotal)
|
||||
return [];
|
||||
return Array.from(Array(7).keys()).map(j => {
|
||||
const day = i * 7 + j - firstWeekday + 1 as JulianDay;
|
||||
if (day < 1 || day > daysTotal)
|
||||
return <div key={j} className="DayFiller"/>;
|
||||
return <div key={j} className="DayOuter">
|
||||
<Day year={year} month={month} day={day} todayJDN={todayJDN}/>
|
||||
</div>;
|
||||
});
|
||||
})
|
||||
}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export type CalendarProps = MonthProps & {
|
||||
todayJDN: number;
|
||||
onSwitch?: (year: number, month: JulianMonth) => void,
|
||||
};
|
||||
|
||||
type CalendarState = {
|
||||
selecting: boolean,
|
||||
yearStr: string,
|
||||
};
|
||||
|
||||
export class Calendar extends React.Component<CalendarProps, CalendarState> {
|
||||
selection: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: CalendarProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selecting: false,
|
||||
yearStr: this.props.year.toString(),
|
||||
};
|
||||
this.selection = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleClickOutside, true);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleClickOutside, true);
|
||||
}
|
||||
|
||||
private goToNormalized(year: number, month: number) {
|
||||
if (month < 1) {
|
||||
--year;
|
||||
month += 12;
|
||||
}
|
||||
|
||||
if (month > 12) {
|
||||
++year;
|
||||
month -= 12;
|
||||
}
|
||||
|
||||
this.props.onSwitch && this.props.onSwitch(year, month as JulianMonth);
|
||||
}
|
||||
|
||||
prevYear = () => {
|
||||
this.goToNormalized(this.props.year - 1, this.props.month);
|
||||
};
|
||||
|
||||
prevMonth = () => {
|
||||
this.goToNormalized(this.props.year, this.props.month - 1);
|
||||
};
|
||||
|
||||
nextYear = () => {
|
||||
this.goToNormalized(this.props.year + 1, this.props.month);
|
||||
};
|
||||
|
||||
nextMonth = () => {
|
||||
this.goToNormalized(this.props.year, this.props.month + 1);
|
||||
};
|
||||
|
||||
startSelection = () => {
|
||||
this.setState({selecting: true});
|
||||
};
|
||||
|
||||
handleClickOutside = (event: any) => {
|
||||
if (this.state.selecting && this.selection.current && !this.selection.current.contains(event.target))
|
||||
this.setState({selecting: false});
|
||||
};
|
||||
|
||||
handleKeyUp = (event: any) => {
|
||||
if (event.key === 'Escape')
|
||||
this.setState({selecting: false});
|
||||
};
|
||||
|
||||
monthChange = (event: any) => {
|
||||
this.goToNormalized(this.props.year, +event.target.value as JulianMonth);
|
||||
};
|
||||
|
||||
yearChange = (event: any) => {
|
||||
if (/^-?\d+/.test(event.target.value)) {
|
||||
this.goToNormalized(+event.target.value, this.props.month);
|
||||
}
|
||||
this.setState({yearStr: event.target.value});
|
||||
};
|
||||
|
||||
goToToday = () => {
|
||||
const [year, month] = jdnJulian(this.props.todayJDN);
|
||||
this.goToNormalized(year, month);
|
||||
this.setState({selecting: false});
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: CalendarProps) {
|
||||
if (prevProps.year !== this.props.year) {
|
||||
const yearStr = this.props.year.toString();
|
||||
if (this.state.yearStr !== yearStr) {
|
||||
this.setState({
|
||||
yearStr: yearStr,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
return <div className="Calendar">
|
||||
<div className="Calendar-head">
|
||||
<div className="Calendar-prev">
|
||||
<button type="button" className="btn btn-secondary" title="Previous year" onClick={this.prevYear}>«
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" title="Previous month"
|
||||
onClick={this.prevMonth}>‹
|
||||
</button>
|
||||
</div>
|
||||
{!this.state.selecting && <div className="Calendar-month-name" onClick={this.startSelection}>
|
||||
{monthName(this.props.month)} {this.props.year}
|
||||
</div>}
|
||||
{this.state.selecting && <div className="Calendar-month-name input-group" ref={this.selection}
|
||||
onKeyUp={this.handleKeyUp}>
|
||||
<select className="Calendar-month-input form-control" onChange={this.monthChange}
|
||||
value={this.props.month}>{
|
||||
Array.from(Array(12).keys()).map(i => {
|
||||
const month = i + 1 as JulianMonth;
|
||||
return <option key={i} value={month}>{monthName(month)}</option>;
|
||||
})
|
||||
}</select>
|
||||
<input type="number" className="Calendar-year-input form-control" value={this.state.yearStr}
|
||||
onChange={this.yearChange}/>
|
||||
<button type="button" className="form-control btn btn-primary Calendar-today-button"
|
||||
onClick={this.goToToday}>Today
|
||||
</button>
|
||||
</div>}
|
||||
<div className="Calendar-next">
|
||||
<button type="button" className="btn btn-secondary" title="Next month" onClick={this.nextMonth}>›
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" title="Next year" onClick={this.nextYear}>»
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Month year={this.props.year} month={this.props.month} todayJDN={this.props.todayJDN}/>
|
||||
</div>;
|
||||
}
|
||||
}
|
2
jcal/src/consts.scss
Normal file
2
jcal/src/consts.scss
Normal file
|
@ -0,0 +1,2 @@
|
|||
$calendar-width: 1600px;
|
||||
$calendar-gutter: 0.5rem;
|
99
jcal/src/index.scss
Normal file
99
jcal/src/index.scss
Normal file
|
@ -0,0 +1,99 @@
|
|||
@import 'bootstrap/scss/functions';
|
||||
@import 'bootstrap/scss/variables';
|
||||
@import 'bootstrap/scss/mixins';
|
||||
@import 'bootstrap/scss/reboot';
|
||||
@import 'bootstrap/scss/navbar';
|
||||
@import 'bootstrap/scss/transitions';
|
||||
@import 'bootstrap/scss/type';
|
||||
@import 'bootstrap/scss/card';
|
||||
@import 'bootstrap/scss/forms';
|
||||
@import 'bootstrap/scss/root';
|
||||
@import './consts';
|
||||
|
||||
body {
|
||||
padding-top: $spacer * 4;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#root {
|
||||
max-width: $calendar-width;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
nav.navbar {
|
||||
background: $light;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.container {
|
||||
max-width: $calendar-width;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.hide-small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navigate {
|
||||
max-width: $calendar-width;
|
||||
margin-top: $spacer;
|
||||
@include make-container();
|
||||
|
||||
.go-year {
|
||||
max-width: 7em;
|
||||
}
|
||||
|
||||
.go-month, .go-day {
|
||||
max-width: 5em;
|
||||
}
|
||||
|
||||
.go-button {
|
||||
max-width: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
max-width: $calendar-width;
|
||||
margin-top: $spacer;
|
||||
@include make-container();
|
||||
|
||||
.card {
|
||||
margin-bottom: $spacer;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-padding-top: $spacer * 4;
|
||||
}
|
||||
|
||||
@supports not (scroll-padding-top: 80px) {
|
||||
h1, h2, h3, h4, h5, h6, .footnotes li {
|
||||
margin-top: -$spacer * 4;
|
||||
padding-top: $spacer * 4;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: $light;
|
||||
min-height: $spacer * 4;
|
||||
padding: ($spacer * 4 - $font-size-base) / 2 0;
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: $calendar-width;
|
||||
margin: 0 auto;
|
||||
padding: 0 1em;
|
||||
}
|
||||
}
|
18
jcal/src/index.tsx
Normal file
18
jcal/src/index.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import 'bootstrap/js/dist/collapse';
|
||||
import './index.scss';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
1
jcal/src/react-app-env.d.ts
vendored
Normal file
1
jcal/src/react-app-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="react-scripts" />
|
15
jcal/src/reportWebVitals.ts
Normal file
15
jcal/src/reportWebVitals.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
5
jcal/src/setupTests.ts
Normal file
5
jcal/src/setupTests.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
27
jcal/tsconfig.json
Normal file
27
jcal/tsconfig.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"extends": "./tsconfig.paths.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
8
jcal/tsconfig.paths.json
Normal file
8
jcal/tsconfig.paths.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@common/*": ["../common/src/*"]
|
||||
}
|
||||
}
|
||||
}
|
17480
package-lock.json
generated
Normal file
17480
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
8
package.json
Normal file
8
package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "qcal",
|
||||
"workspaces": [
|
||||
"common",
|
||||
"frcal",
|
||||
"jcal"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue