mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-10 11:09:46 -05:00
Compare commits
212 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57027a1a62 | |||
| 92b4a8029d | |||
| 7b4dd36827 | |||
| be65042463 | |||
| cccb6a9c02 | |||
| 916c7c49e7 | |||
| 164121d9e4 | |||
| a2d209a34b | |||
| c8adf20804 | |||
| de132c59ea | |||
| e5f6c4ba0f | |||
| 360c17c56e | |||
| c8b98678d0 | |||
| ea7e5d2a8d | |||
| afee09e7cb | |||
| 01fea6b968 | |||
| 2df6b6461b | |||
| 8c96ac241a | |||
| c0362c614e | |||
| 229ed46425 | |||
| 7363f08a86 | |||
| 2322f7e342 | |||
| 7ede3acd01 | |||
| 57f17a08e8 | |||
| 63453ce01b | |||
| 888f6f2486 | |||
| e8501f5cf8 | |||
| fc49015ff0 | |||
| eb4e930e63 | |||
| 53c536b525 | |||
| e2defc675e | |||
| f0fa2508a9 | |||
| 232a62ffc7 | |||
| d2d4af227b | |||
| ca1200b689 | |||
| 008579363c | |||
| 786b1ec670 | |||
| 573de80a91 | |||
| 115340e62d | |||
| 5de2573521 | |||
| b472af532c | |||
| b264e539f4 | |||
| c726208d6e | |||
| b1d2fff13f | |||
| 027e61a93e | |||
| c35afb7cfe | |||
| 8cd706fd95 | |||
| 3a4620976e | |||
| a16098ccda | |||
| 118c8421fe | |||
| a363e68080 | |||
| 1940062200 | |||
| aea075d0bf | |||
| 7388707a43 | |||
| 3e63d74b2c | |||
| 199ce3e6f7 | |||
| 597f74c84b | |||
| c83a8c9766 | |||
| c481354f78 | |||
| ec9973f9d2 | |||
| 971b124b05 | |||
| de3f1b3f5e | |||
| 7098d2a72e | |||
| bbea789700 | |||
| 5359a2bf3d | |||
| 93e922e774 | |||
| 377b152ffd | |||
| 8ff8213954 | |||
| 00bdf81902 | |||
| 4ae50e4d44 | |||
| 383cd84016 | |||
| f6f1202baf | |||
| 0c65ff6fec | |||
| 83f5c92ff1 | |||
| 8dcacdc92e | |||
| 7874ae5092 | |||
| 481719e513 | |||
| 9657112bca | |||
| f7026b2478 | |||
| e39b3bdd35 | |||
| a210ab57b0 | |||
| 3f3e0e2ae8 | |||
| 65f12ac9ea | |||
| d0941810a7 | |||
| b77deb28b4 | |||
| 458aee9a3a | |||
| 0b6b967753 | |||
| 57f86bac70 | |||
| f2fe83a469 | |||
| 3354ca048c | |||
| 99c46602c4 | |||
| ee585c0ff3 | |||
| 0754c0ff05 | |||
| 5d1346e65c | |||
| a2ce0ab099 | |||
| 16ae182f34 | |||
| 6887fd70c0 | |||
| f327795761 | |||
| 8cf5a380da | |||
| def24142ca | |||
| c29049146d | |||
| cc419385f6 | |||
| 45107fe48f | |||
| 80f517f1ad | |||
| 57fb7ca6f2 | |||
| 62b171ffa5 | |||
| be18476292 | |||
| b6e9d0c9c0 | |||
| 442d9ca9cd | |||
| 3f56b7fa3f | |||
| 14f9debfdb | |||
| 4a1011f1af | |||
| d69de2948b | |||
| c5f5da1d32 | |||
| e338fb8ce7 | |||
| 2d5dcae406 | |||
| 9ec1917e6d | |||
| 409ce5dbfb | |||
| ab706abed5 | |||
| 3203b09b7a | |||
| 2ea023299c | |||
| ce1ce47d18 | |||
| 0d1e8ec3f9 | |||
| 4a849e6d15 | |||
| 7f65079cfe | |||
| 04f54d5e03 | |||
| 925e2e5ec6 | |||
| a549668522 | |||
| 02e0e45da6 | |||
| 1e72dbe155 | |||
| e71f0558fe | |||
| b3d83431e6 | |||
| 2ac21104a4 | |||
| 6b95f648c4 | |||
| f9db4b9b5b | |||
| 6ee2e3a379 | |||
| 74da6d8798 | |||
| a73e4d399e | |||
| 8f4d668b0f | |||
| 945c19cc80 | |||
| 7b4cfee290 | |||
| 77229f3e5e | |||
| 41cdab1672 | |||
| 58f4dd0c43 | |||
| 0ce64a0197 | |||
| 0b8f2bc58e | |||
| 015631685b | |||
| 6c536c0b89 | |||
| 1de2adf301 | |||
| 0335eb1f7e | |||
| e0a5938711 | |||
| ad6555c92b | |||
| c04e8ea514 | |||
| aec2409227 | |||
| 87aebcc19e | |||
| a3bc20f855 | |||
| 86e33b2364 | |||
| 12479edb77 | |||
| c0c6657536 | |||
| e81a052f66 | |||
| 82a1d6ff0e | |||
| 0f7001b609 | |||
| 87558a325e | |||
| de48925341 | |||
| 614850e56c | |||
| 64a3515c10 | |||
| 8dfa21a4b8 | |||
| f9a9d4919b | |||
| ddf1b4060d | |||
| 967717a010 | |||
| 9b791b4ba0 | |||
| 5aca5b4be7 | |||
| 0dd25b6431 | |||
| cf75d941fa | |||
| 777f7887b4 | |||
| f07d0f6441 | |||
| 98ec1757f9 | |||
| 742da1f2c6 | |||
| b3d5a8d083 | |||
| b5f2e66025 | |||
| 9a40674d8d | |||
| c7deb1eb19 | |||
| a213fb723a | |||
| 0f9b6ab591 | |||
| cde5fbef85 | |||
| 8b2af1ef56 | |||
| 21652c2670 | |||
| d1ee679810 | |||
| 67988da33c | |||
| fae26a517d | |||
| e3c86349b4 | |||
| 6604f38144 | |||
| 037882b50a | |||
| 15deb778fd | |||
| 7d2529f5e1 | |||
| 8d732c59c4 | |||
| 3a34aa4cc5 | |||
| e7fc7feddd | |||
| 7fd899b642 | |||
| 36d2ad6b9b | |||
| 164dbdcf10 | |||
| b65fa941b9 | |||
| ab953440e3 | |||
| 1143f690d1 | |||
| 08469c556b | |||
| 13a25ad89e | |||
| 8e2e170930 | |||
| e6a7d15644 | |||
| 6a4b08203f | |||
| c9016c8d42 | |||
| 31685c3e94 | |||
| c25b09c7ed |
+2
-1
@@ -86,5 +86,6 @@
|
||||
"RATE_LIMITER_ENABLED": "false",
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678"
|
||||
"REDIS_PASSWORD": "12345678",
|
||||
"TRUSTED_DOMAINS": "https://localhost,https://habitica.com"
|
||||
}
|
||||
|
||||
+1
-1
Submodule habitica-images updated: 7203f4633c...4ec7469f72
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20220314_pi_day';
|
||||
const MIGRATION_NAME = '20230314_pi_day';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
@@ -54,7 +54,7 @@ async function updateUser (user) {
|
||||
export default async function processUsers () {
|
||||
const query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2022-02-15') },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-02-15') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
|
||||
Generated
+234
-229
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "4.258.1",
|
||||
"version": "4.267.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1667,15 +1667,135 @@
|
||||
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
|
||||
},
|
||||
"@esbuild/android-arm": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.12.tgz",
|
||||
"integrity": "sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==",
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.12.tgz",
|
||||
"integrity": "sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==",
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
|
||||
"integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-mips64el": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
|
||||
"integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ppc64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
|
||||
"integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-riscv64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
|
||||
"integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-s390x": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
|
||||
"integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/netbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/openbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/sunos-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
|
||||
"optional": true
|
||||
},
|
||||
"@google-cloud/common": {
|
||||
@@ -2147,9 +2267,9 @@
|
||||
}
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "8.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.7.tgz",
|
||||
"integrity": "sha512-ehM7cCt2RSFs42mb+lcmhFT9ouIlV92PuaeRGn8N8c98oMjG4Z5pJHA9b1QiCcuqnbPSHcyfiD3mlhqMaHsQIw==",
|
||||
"version": "8.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.0.tgz",
|
||||
"integrity": "sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==",
|
||||
"requires": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
@@ -2636,9 +2756,9 @@
|
||||
}
|
||||
},
|
||||
"apidoc": {
|
||||
"version": "0.53.1",
|
||||
"resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.53.1.tgz",
|
||||
"integrity": "sha512-ijiLtIVEzTMdF29B/QzkvR4weMatgcElMsYKP1asszrImWYwzlZ9x0ZMLTXZrCe7GVMtkGSwQdugdLTZMZ+lww==",
|
||||
"version": "0.54.0",
|
||||
"resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.54.0.tgz",
|
||||
"integrity": "sha512-VCOdwkAaFK7bDLbiAiFKqX8SVlAnk7sQCXDARwshBQpVRqRGDNY993kAMPkWmSD1RCVBDMFIOgOfOR9mcISNZQ==",
|
||||
"requires": {
|
||||
"bootstrap": "3.4.1",
|
||||
"commander": "^8.3.0",
|
||||
@@ -2663,9 +2783,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
@@ -4075,20 +4195,25 @@
|
||||
"dev": true
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.2.tgz",
|
||||
"integrity": "sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==",
|
||||
"version": "4.21.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
|
||||
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001366",
|
||||
"electron-to-chromium": "^1.4.188",
|
||||
"node-releases": "^2.0.6",
|
||||
"update-browserslist-db": "^1.0.4"
|
||||
"caniuse-lite": "^1.0.30001449",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"node-releases": "^2.0.8",
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.192",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.192.tgz",
|
||||
"integrity": "sha512-8nCXyIQY9An88NXAp+PuPy5h3/w5ZY7Iu2lag65Q0XREprcat5F8gKhoHsBUnQcFuCRnmevpR8yEBYRU3d2HDw=="
|
||||
"version": "1.4.295",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.295.tgz",
|
||||
"integrity": "sha512-lEO94zqf1bDA3aepxwnWoHUjA8sZ+2owgcSZjYQy0+uOSEclJX0VieZC+r+wLpSxUHRd6gG32znTWmr+5iGzFw=="
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
|
||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4246,9 +4371,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001367",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001367.tgz",
|
||||
"integrity": "sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw=="
|
||||
"version": "1.0.30001451",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz",
|
||||
"integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w=="
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
@@ -4879,9 +5004,9 @@
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
|
||||
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ=="
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="
|
||||
},
|
||||
"cookies": {
|
||||
"version": "0.8.0",
|
||||
@@ -5759,9 +5884,9 @@
|
||||
}
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
|
||||
"integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
|
||||
"version": "5.12.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
|
||||
"integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@@ -5871,167 +5996,47 @@
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.12.tgz",
|
||||
"integrity": "sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng==",
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
|
||||
"integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
|
||||
"requires": {
|
||||
"@esbuild/android-arm": "0.15.12",
|
||||
"@esbuild/linux-loong64": "0.15.12",
|
||||
"esbuild-android-64": "0.15.12",
|
||||
"esbuild-android-arm64": "0.15.12",
|
||||
"esbuild-darwin-64": "0.15.12",
|
||||
"esbuild-darwin-arm64": "0.15.12",
|
||||
"esbuild-freebsd-64": "0.15.12",
|
||||
"esbuild-freebsd-arm64": "0.15.12",
|
||||
"esbuild-linux-32": "0.15.12",
|
||||
"esbuild-linux-64": "0.15.12",
|
||||
"esbuild-linux-arm": "0.15.12",
|
||||
"esbuild-linux-arm64": "0.15.12",
|
||||
"esbuild-linux-mips64le": "0.15.12",
|
||||
"esbuild-linux-ppc64le": "0.15.12",
|
||||
"esbuild-linux-riscv64": "0.15.12",
|
||||
"esbuild-linux-s390x": "0.15.12",
|
||||
"esbuild-netbsd-64": "0.15.12",
|
||||
"esbuild-openbsd-64": "0.15.12",
|
||||
"esbuild-sunos-64": "0.15.12",
|
||||
"esbuild-windows-32": "0.15.12",
|
||||
"esbuild-windows-64": "0.15.12",
|
||||
"esbuild-windows-arm64": "0.15.12"
|
||||
"@esbuild/android-arm": "0.16.17",
|
||||
"@esbuild/android-arm64": "0.16.17",
|
||||
"@esbuild/android-x64": "0.16.17",
|
||||
"@esbuild/darwin-arm64": "0.16.17",
|
||||
"@esbuild/darwin-x64": "0.16.17",
|
||||
"@esbuild/freebsd-arm64": "0.16.17",
|
||||
"@esbuild/freebsd-x64": "0.16.17",
|
||||
"@esbuild/linux-arm": "0.16.17",
|
||||
"@esbuild/linux-arm64": "0.16.17",
|
||||
"@esbuild/linux-ia32": "0.16.17",
|
||||
"@esbuild/linux-loong64": "0.16.17",
|
||||
"@esbuild/linux-mips64el": "0.16.17",
|
||||
"@esbuild/linux-ppc64": "0.16.17",
|
||||
"@esbuild/linux-riscv64": "0.16.17",
|
||||
"@esbuild/linux-s390x": "0.16.17",
|
||||
"@esbuild/linux-x64": "0.16.17",
|
||||
"@esbuild/netbsd-x64": "0.16.17",
|
||||
"@esbuild/openbsd-x64": "0.16.17",
|
||||
"@esbuild/sunos-x64": "0.16.17",
|
||||
"@esbuild/win32-arm64": "0.16.17",
|
||||
"@esbuild/win32-ia32": "0.16.17",
|
||||
"@esbuild/win32-x64": "0.16.17"
|
||||
}
|
||||
},
|
||||
"esbuild-android-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.12.tgz",
|
||||
"integrity": "sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-android-arm64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.12.tgz",
|
||||
"integrity": "sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.12.tgz",
|
||||
"integrity": "sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-arm64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.12.tgz",
|
||||
"integrity": "sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.12.tgz",
|
||||
"integrity": "sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-arm64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.12.tgz",
|
||||
"integrity": "sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-32": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.12.tgz",
|
||||
"integrity": "sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.12.tgz",
|
||||
"integrity": "sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.12.tgz",
|
||||
"integrity": "sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.12.tgz",
|
||||
"integrity": "sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-mips64le": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.12.tgz",
|
||||
"integrity": "sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-ppc64le": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.12.tgz",
|
||||
"integrity": "sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-riscv64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.12.tgz",
|
||||
"integrity": "sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-s390x": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.12.tgz",
|
||||
"integrity": "sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-loader": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.20.0.tgz",
|
||||
"integrity": "sha512-dr+j8O4w5RvqZ7I4PPB4EIyVTd679EBQnMm+JBB7av+vu05Zpje2IpK5N3ld1VWa+WxrInIbNFAg093+E1aRsA==",
|
||||
"version": "2.21.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.21.0.tgz",
|
||||
"integrity": "sha512-k7ijTkCT43YBSZ6+fBCW1Gin7s46RrJ0VQaM8qA7lq7W+OLsGgtLyFV8470FzYi/4TeDexniTBTPTwZUnXXR5g==",
|
||||
"requires": {
|
||||
"esbuild": "^0.15.6",
|
||||
"esbuild": "^0.16.17",
|
||||
"joycon": "^3.0.1",
|
||||
"json5": "^2.2.0",
|
||||
"loader-utils": "^2.0.0",
|
||||
"tapable": "^2.2.0",
|
||||
"webpack-sources": "^2.2.0"
|
||||
"webpack-sources": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"esbuild-netbsd-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.12.tgz",
|
||||
"integrity": "sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-openbsd-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.12.tgz",
|
||||
"integrity": "sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.12.tgz",
|
||||
"integrity": "sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-32": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.12.tgz",
|
||||
"integrity": "sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.12.tgz",
|
||||
"integrity": "sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA==",
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-arm64": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.12.tgz",
|
||||
"integrity": "sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA==",
|
||||
"optional": true
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
@@ -7729,9 +7734,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
|
||||
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
|
||||
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
@@ -7749,9 +7754,9 @@
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
@@ -9704,9 +9709,9 @@
|
||||
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz",
|
||||
"integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw=="
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
|
||||
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
@@ -9784,9 +9789,9 @@
|
||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
|
||||
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ=="
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
@@ -13451,9 +13456,9 @@
|
||||
}
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
|
||||
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
|
||||
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
@@ -13612,9 +13617,9 @@
|
||||
}
|
||||
},
|
||||
"simple-update-notifier": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz",
|
||||
"integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
|
||||
"requires": {
|
||||
"semver": "~7.0.0"
|
||||
},
|
||||
@@ -14286,9 +14291,9 @@
|
||||
}
|
||||
},
|
||||
"stripe": {
|
||||
"version": "11.6.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-11.6.0.tgz",
|
||||
"integrity": "sha512-ht8S1l8CJJE3jtv2NM1mEQzZBkITYvb9uDpSeXYeNz9iJkFFgDU169htwOW00OdIESvFaIsGgWQatAE5dfOERQ==",
|
||||
"version": "11.10.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-11.10.0.tgz",
|
||||
"integrity": "sha512-uzQuj/Vangpp8fgkaEr6oRNfPToo5xamOhc7wxFGF/RUm7VKm/IGzoZ0cCtnGHCs5/O1AjMQsTZApgUARRVHOw==",
|
||||
"requires": {
|
||||
"@types/node": ">=8.1.0",
|
||||
"qs": "^6.11.0"
|
||||
@@ -14600,9 +14605,9 @@
|
||||
"integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw=="
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.15.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz",
|
||||
"integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==",
|
||||
"version": "5.16.3",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz",
|
||||
"integrity": "sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==",
|
||||
"requires": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
@@ -14611,9 +14616,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
|
||||
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w=="
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
@@ -15310,9 +15315,9 @@
|
||||
"integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="
|
||||
},
|
||||
"update-browserslist-db": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
|
||||
"integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==",
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||
"requires": {
|
||||
"escalade": "^3.1.1",
|
||||
"picocolors": "^1.0.0"
|
||||
@@ -15514,9 +15519,9 @@
|
||||
}
|
||||
},
|
||||
"validator": {
|
||||
"version": "13.7.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
|
||||
"integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
|
||||
"version": "13.9.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz",
|
||||
"integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA=="
|
||||
},
|
||||
"value-or-function": {
|
||||
"version": "3.0.0",
|
||||
@@ -15782,9 +15787,9 @@
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.74.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
|
||||
"integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
|
||||
"version": "5.75.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
|
||||
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
|
||||
"requires": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
"@types/estree": "^0.0.51",
|
||||
@@ -15813,9 +15818,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
|
||||
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w=="
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
@@ -15902,12 +15907,12 @@
|
||||
}
|
||||
},
|
||||
"webpack-sources": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz",
|
||||
"integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==",
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
||||
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
||||
"requires": {
|
||||
"source-list-map": "^2.0.1",
|
||||
"source-map": "^0.6.1"
|
||||
"source-list-map": "^2.0.0",
|
||||
"source-map": "~0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.258.1",
|
||||
"version": "4.267.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.12",
|
||||
@@ -13,7 +13,7 @@
|
||||
"accepts": "^1.3.8",
|
||||
"amazon-payments": "^0.2.9",
|
||||
"amplitude": "^6.0.0",
|
||||
"apidoc": "^0.53.1",
|
||||
"apidoc": "^0.54.0",
|
||||
"apple-auth": "^1.0.7",
|
||||
"bcrypt": "^5.1.0",
|
||||
"body-parser": "^1.20.1",
|
||||
@@ -30,7 +30,7 @@
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-validator": "^5.2.0",
|
||||
"glob": "^8.0.3",
|
||||
"glob": "^8.1.0",
|
||||
"got": "^11.8.3",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
@@ -67,12 +67,12 @@
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"stripe": "^11.6.0",
|
||||
"stripe": "^11.10.0",
|
||||
"superagent": "^8.0.6",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.7.0",
|
||||
"validator": "^13.9.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.8.2",
|
||||
"winston-loggly-bulk": "^3.2.1",
|
||||
|
||||
+100
-21
@@ -231,13 +231,16 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user1 has a 1-month recurring subscription starting today
|
||||
user1.purchased.plan.customerId = 'subscribedId';
|
||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||
user1.purchased.plan.planId = 'basic';
|
||||
user1.purchased.plan.consecutive.count = 0;
|
||||
user1.purchased.plan.consecutive.offset = 0;
|
||||
user1.purchased.plan.consecutive.trinkets = 0;
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
beforeEach(async () => {
|
||||
user1.purchased.plan.customerId = 'subscribedId';
|
||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||
user1.purchased.plan.planId = 'basic';
|
||||
user1.purchased.plan.consecutive.count = 0;
|
||||
user1.purchased.plan.perkMonthCount = 0;
|
||||
user1.purchased.plan.consecutive.offset = 0;
|
||||
user1.purchased.plan.consecutive.trinkets = 0;
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits after the first month', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -271,6 +274,24 @@ describe('cron', async () => {
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => {
|
||||
user1.purchased.plan.perkMonthCount = 1;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
||||
// e.g., from time zone oddness.
|
||||
await cron({
|
||||
user: user1, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user1.purchased.plan.perkMonthCount).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(2);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the third month', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||
.add(2, 'days')
|
||||
@@ -315,6 +336,30 @@ describe('cron', async () => {
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||
});
|
||||
|
||||
it('initializes plan.perkMonthCount if necessary', async () => {
|
||||
user.purchased.plan.perkMonthCount = undefined;
|
||||
clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated)
|
||||
.utcOffset(0)
|
||||
.startOf('month')
|
||||
.add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.perkMonthCount).to.equal(1);
|
||||
user.purchased.plan.perkMonthCount = undefined;
|
||||
user.purchased.plan.consecutive.count = 8;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.perkMonthCount).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 3-month recurring subscription', async () => {
|
||||
@@ -330,13 +375,16 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user3 has a 3-month recurring subscription starting today
|
||||
user3.purchased.plan.customerId = 'subscribedId';
|
||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3.purchased.plan.planId = 'basic_3mo';
|
||||
user3.purchased.plan.consecutive.count = 0;
|
||||
user3.purchased.plan.consecutive.offset = 3;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
beforeEach(async () => {
|
||||
user3.purchased.plan.customerId = 'subscribedId';
|
||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3.purchased.plan.planId = 'basic_3mo';
|
||||
user3.purchased.plan.perkMonthCount = 0;
|
||||
user3.purchased.plan.consecutive.count = 0;
|
||||
user3.purchased.plan.consecutive.offset = 3;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -390,6 +438,21 @@ describe('cron', async () => {
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
});
|
||||
|
||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
||||
user3.purchased.plan.perkMonthCount = 2;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user3, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user3.purchased.plan.perkMonthCount).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
|
||||
.add(2, 'days')
|
||||
@@ -456,13 +519,16 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user6 has a 6-month recurring subscription starting today
|
||||
user6.purchased.plan.customerId = 'subscribedId';
|
||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6.purchased.plan.planId = 'google_6mo';
|
||||
user6.purchased.plan.consecutive.count = 0;
|
||||
user6.purchased.plan.consecutive.offset = 6;
|
||||
user6.purchased.plan.consecutive.trinkets = 2;
|
||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||
beforeEach(async () => {
|
||||
user6.purchased.plan.customerId = 'subscribedId';
|
||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6.purchased.plan.planId = 'google_6mo';
|
||||
user6.purchased.plan.perkMonthCount = 0;
|
||||
user6.purchased.plan.consecutive.count = 0;
|
||||
user6.purchased.plan.consecutive.offset = 6;
|
||||
user6.purchased.plan.consecutive.trinkets = 2;
|
||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -503,6 +569,19 @@ describe('cron', async () => {
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
});
|
||||
|
||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
||||
user6.purchased.plan.perkMonthCount = 2;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user6, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user6.purchased.plan.perkMonthCount).to.equal(2);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
||||
.add(2, 'days')
|
||||
|
||||
@@ -29,8 +29,9 @@ describe('Apple Payments', () => {
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iap, 'validate')
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
sinon.stub(iap, 'isCanceled').returns(false);
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||
@@ -44,6 +45,8 @@ describe('Apple Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
iap.isExpired.restore();
|
||||
iap.isCanceled.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.buySkuItem.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
@@ -218,6 +221,7 @@ describe('Apple Payments', () => {
|
||||
headers = {};
|
||||
receipt = `{"token": "${token}"}`;
|
||||
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
||||
user = new User();
|
||||
|
||||
iapSetupStub = sinon.stub(iap, 'setup')
|
||||
.resolves();
|
||||
@@ -228,14 +232,17 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: 'wrongsku',
|
||||
transactionId: token,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
}]);
|
||||
@@ -250,21 +257,12 @@ describe('Apple Payments', () => {
|
||||
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if sku is empty', async () => {
|
||||
await expect(applePayments.subscribe('', user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
iap.isValidated.restore();
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -295,13 +293,15 @@ describe('Apple Payments', () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: new Date(),
|
||||
productId: option.sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[option.subKey];
|
||||
|
||||
await applePayments.subscribe(option.sku, user, receipt, headers, nextPaymentProcessing);
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -321,21 +321,253 @@ describe('Apple Payments', () => {
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
});
|
||||
if (option !== subOptions[3]) {
|
||||
const newOption = subOptions[3];
|
||||
it(`upgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
|
||||
const oldSub = common.content.subscriptionBlocks[option.subKey];
|
||||
oldSub.logic = 'refundAndRepay';
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
user.purchased.plan.customerId = token;
|
||||
user.purchased.plan.planId = option.subKey;
|
||||
user.purchased.plan.additionalData = receipt;
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: newOption.sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
updatedFrom: oldSub,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (option !== subOptions[0]) {
|
||||
const newOption = subOptions[0];
|
||||
it(`downgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
|
||||
const oldSub = common.content.subscriptionBlocks[option.subKey];
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
user.purchased.plan.customerId = token;
|
||||
user.purchased.plan.planId = option.subKey;
|
||||
user.purchased.plan.additionalData = receipt;
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: newOption.sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
updatedFrom: oldSub,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('errors when a user is already subscribed', async () => {
|
||||
payments.createSubscription.restore();
|
||||
user = new User();
|
||||
await user.save();
|
||||
it('uses the most recent subscription data', async () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 4 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 5 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.3month',
|
||||
transactionId: `${token}oldest`,
|
||||
originalTransactionId: `${token}evenOlder`,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.12month',
|
||||
transactionId: `${token}newest`,
|
||||
originalTransactionId: `${token}newest`,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 2 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.6month',
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks.basic_12mo;
|
||||
|
||||
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: `${token}newest`,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
});
|
||||
|
||||
describe('does not apply multiple times', async () => {
|
||||
it('errors when a user is using the same subscription', async () => {
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a user is using a rebill of the same subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: `${token}renew`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a different user is using the subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
const secondUser = new User();
|
||||
await secondUser.save();
|
||||
await expect(applePayments.subscribe(
|
||||
secondUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a multiple users exist using the subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
const secondUser = new User();
|
||||
secondUser.purchased.plan = user.purchased.plan;
|
||||
secondUser.purchased.plan.dateTerminate = new Date();
|
||||
secondUser.save();
|
||||
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
const thirdUser = new User();
|
||||
await thirdUser.save();
|
||||
await expect(applePayments.subscribe(
|
||||
thirdUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -360,9 +592,9 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: expirationDate.toDate() }]);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||
sinon.stub(iap, 'isCanceled').returns(false);
|
||||
sinon.stub(iap, 'isExpired').returns(true);
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
@@ -377,6 +609,8 @@ describe('Apple Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
iap.isExpired.restore();
|
||||
iap.isCanceled.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
@@ -396,6 +630,8 @@ describe('Apple Payments', () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
|
||||
iap.isExpired.restore();
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
|
||||
await expect(applePayments.cancelSubscribe(user, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -418,7 +654,38 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should cancel a user subscription', async () => {
|
||||
it('should cancel a cancelled subscription with termination date in the future', async () => {
|
||||
const futureDate = expirationDate.add({ day: 1 });
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: futureDate }]);
|
||||
iap.isExpired.restore();
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
|
||||
iap.isCanceled.restore();
|
||||
sinon.stub(iap, 'isCanceled').returns(true);
|
||||
|
||||
await applePayments.cancelSubscribe(user, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({
|
||||
expirationDate: futureDate,
|
||||
});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
|
||||
expect(paymentCancelSubscriptionSpy).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
nextBill: futureDate.toDate(),
|
||||
headers,
|
||||
});
|
||||
});
|
||||
|
||||
it('should cancel an expired subscription', async () => {
|
||||
await applePayments.cancelSubscribe(user, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
|
||||
@@ -203,6 +203,28 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.exist;
|
||||
});
|
||||
|
||||
it('keeps plan.dateCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = recipient.purchased.plan.dateCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(recipient.purchased.plan.dateCreated).to.eql(initialDate);
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = recipient.purchased.plan.dateCurrentTypeCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
||||
});
|
||||
|
||||
it('does not change plan.customerId if it already exists', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
data.customerId = 'purchaserCustomerId';
|
||||
@@ -213,6 +235,116 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 1;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if field is not initialized', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = -1;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('adds to plan.perkMonthCount if user is already subscribed', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 1;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => {
|
||||
recipient.purchased.plan.perkMonthCount = 0;
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => {
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount goes over 3', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
data.sub.key = 'basic_earned';
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('sets plan.customerId to "Gift" if it does not already exist', async () => {
|
||||
expect(recipient.purchased.plan.customerId).to.not.exist;
|
||||
|
||||
@@ -379,6 +511,7 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.gemsBought).to.eql(0);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(user.purchased.plan.paymentMethod).to.eql('Payment Method');
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
expect(user.purchased.plan.dateTerminated).to.eql(null);
|
||||
@@ -386,6 +519,63 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||
expect(user.purchased.plan.dateCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.exist;
|
||||
});
|
||||
|
||||
it('keeps plan.dateCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = user.purchased.plan.dateCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.dateCreated).to.eql(initialDate);
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = user.purchased.plan.dateCurrentTypeCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
||||
});
|
||||
|
||||
it('keeps plan.perkMonthCount when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(2);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => {
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => {
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('updates plan.consecutive.offset when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(3);
|
||||
data.sub.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(6);
|
||||
});
|
||||
|
||||
it('awards the Royal Purple Jackalope pet', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -465,6 +655,89 @@ describe('payments/index', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('Upgrades subscription', () => {
|
||||
it('from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom = { key: 'basic_earned' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom = { key: 'basic_3mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
});
|
||||
|
||||
context('Downgrades subscription', () => {
|
||||
it('from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Block subscription perks', () => {
|
||||
@@ -488,7 +761,6 @@ describe('payments/index', () => {
|
||||
|
||||
it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
@@ -496,7 +768,6 @@ describe('payments/index', () => {
|
||||
|
||||
it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => {
|
||||
data.sub.key = 'basic_12mo';
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
@@ -532,6 +803,532 @@ describe('payments/index', () => {
|
||||
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
context('Upgrades subscription', () => {
|
||||
context('Using payDifference logic', () => {
|
||||
beforeEach(async () => {
|
||||
data.updatedFrom = { logic: 'payDifference' };
|
||||
});
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
|
||||
context('Using payFull logic', () => {
|
||||
beforeEach(async () => {
|
||||
data.updatedFrom = { logic: 'payFull' };
|
||||
});
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
context('Using refundAndRepay logic', () => {
|
||||
let clock;
|
||||
beforeEach(async () => {
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-01'));
|
||||
data.updatedFrom = { logic: 'refundAndRepay' };
|
||||
});
|
||||
context('Upgrades within first half of subscription', () => {
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-10'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-02-05'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-08'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-31'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-08'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-08-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-07-31'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
context('Upgrades within second half of subscription', () => {
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-20'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-02-24'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-03-03'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2023-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2023-09-03'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (clock !== null) clock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Downgrades subscription', () => {
|
||||
it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Mystery Items', () => {
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('payments/skuItems', () => {
|
||||
describe('#gryphatrice', () => {
|
||||
const sku = 'Pet-Gryphatrice-Jubilant';
|
||||
it('returns true during birthday week', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-29'));
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-31'));
|
||||
expect(canBuySkuItem(sku, user)).to.be.true;
|
||||
});
|
||||
it('returns false outside of birthday week', () => {
|
||||
|
||||
@@ -1359,6 +1359,7 @@ describe('Group Model', () => {
|
||||
describe('#sendChat', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateMany');
|
||||
});
|
||||
|
||||
it('formats message', () => {
|
||||
@@ -1413,8 +1414,8 @@ describe('Group Model', () => {
|
||||
it('updates users about new messages in party', () => {
|
||||
party.sendChat({ message: 'message' });
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: '' },
|
||||
});
|
||||
@@ -1427,8 +1428,8 @@ describe('Group Model', () => {
|
||||
|
||||
group.sendChat({ message: 'message' });
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
guilds: group._id,
|
||||
_id: { $ne: '' },
|
||||
});
|
||||
@@ -1437,8 +1438,8 @@ describe('Group Model', () => {
|
||||
it('does not send update to user that sent the message', () => {
|
||||
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: 'user-id' },
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
members: 2,
|
||||
});
|
||||
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
@@ -76,12 +76,17 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/flag`);
|
||||
|
||||
// first test that the flag was actually successful
|
||||
// author always sees own message; flag count is hidden from non-admins
|
||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].flagCount).to.eql(5);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
messages = await members[1].get(`/groups/${group._id}/chat`);
|
||||
expect(messages.length).to.eql(0);
|
||||
|
||||
// admin cannot directly request private group chat, but after unflag,
|
||||
// message should be revealed again and still have flagCount of 0
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/clearflags`);
|
||||
|
||||
messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
messages = await members[1].get(`/groups/${group._id}/chat`);
|
||||
expect(messages.length).to.eql(1);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -45,11 +45,10 @@ describe('payments : apple #subscribe', () => {
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0]).to.eql(sku);
|
||||
expect(subscribeStub.args[0][1]._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][2]).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][3]['x-api-user']).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0]._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][1]).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][2]['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,13 +35,6 @@ describe('GET /world-state', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a string representing the current season for NPC sprites', async () => {
|
||||
const res = await requester().get('/world-state');
|
||||
|
||||
expect(res).to.have.nested.property('npcImageSuffix');
|
||||
expect(res.npcImageSuffix).to.be.a('string');
|
||||
});
|
||||
|
||||
context('no current event', () => {
|
||||
beforeEach(async () => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns(null);
|
||||
|
||||
@@ -215,6 +215,7 @@ describe('cron utility functions', () => {
|
||||
|
||||
it('monthly plan, next date in 3 months', () => {
|
||||
const user = baseUserData(60, 0, 'group_plan_auto');
|
||||
user.purchased.plan.perkMonthCount = 0;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
@@ -224,6 +225,7 @@ describe('cron utility functions', () => {
|
||||
|
||||
it('monthly plan, next date in 1 month', () => {
|
||||
const user = baseUserData(62, 0, 'group_plan_auto');
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
@@ -248,5 +250,15 @@ describe('cron utility functions', () => {
|
||||
expect(planContext.nextHourglassDate)
|
||||
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
|
||||
});
|
||||
|
||||
it('multi-month plan with perk count', () => {
|
||||
const user = baseUserData(60, 1, 'basic_3mo');
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
expect(planContext.nextHourglassDate)
|
||||
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,8 +12,9 @@ const webhookData = {};
|
||||
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true,
|
||||
limit: '10mb',
|
||||
}));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.json({ limit: '10mb' }));
|
||||
|
||||
app.post('/webhooks/:id', (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
Generated
+73
-88
@@ -4979,9 +4979,9 @@
|
||||
}
|
||||
},
|
||||
"@sideway/formula": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
|
||||
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="
|
||||
},
|
||||
"@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
@@ -13318,11 +13318,34 @@
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"optional": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"optional": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
|
||||
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
@@ -13354,6 +13377,38 @@
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.8.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
|
||||
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
@@ -17903,9 +17958,9 @@
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
|
||||
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA=="
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
||||
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
@@ -20652,9 +20707,9 @@
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||
},
|
||||
"hellojs": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/hellojs/-/hellojs-1.19.5.tgz",
|
||||
"integrity": "sha512-hFlublej5rHFWjGe6MMMmj78otSIXBbrfvtWPZSSRmXKFoLMFlL41PJ1JPS8xwiSIZca2ve8uHoPZUDwU+/8gg=="
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/hellojs/-/hellojs-1.20.0.tgz",
|
||||
"integrity": "sha512-zfZGRt0J0OpJHnw2GJz4uh+rzMNAMWpIymiaRbSmCqCvqDMLSx2/qR5JucqnHPsZHUEjyKj5aLJ8K54kyHHjLQ=="
|
||||
},
|
||||
"hex-color-regex": {
|
||||
"version": "1.1.0",
|
||||
@@ -28120,9 +28175,9 @@
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"stopword": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.5.tgz",
|
||||
"integrity": "sha512-MgmxgmVs0Uo9G4mMqRc/QBXdPePZUnVNYqnNiv8QdCXTqHmGRw36mrjToCeNxhu/7Ifa7RxAtd/KR/GLZdnN9g=="
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.7.tgz",
|
||||
"integrity": "sha512-s+uLKAxrproCLrq0Wcd3JAIjlJLx6l80b2Rzt0u8+ra5SzGkHnNG8PS3DfGmYk2TrKePDVLL4SdKYwKpgSLc+w=="
|
||||
},
|
||||
"store2": {
|
||||
"version": "2.10.0",
|
||||
@@ -29770,9 +29825,9 @@
|
||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
||||
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ=="
|
||||
"version": "0.7.33",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
|
||||
"integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw=="
|
||||
},
|
||||
"uc.micro": {
|
||||
"version": "1.0.6",
|
||||
@@ -30240,9 +30295,9 @@
|
||||
}
|
||||
},
|
||||
"validator": {
|
||||
"version": "13.7.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
|
||||
"integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
|
||||
"version": "13.9.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz",
|
||||
"integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
@@ -30574,76 +30629,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.8.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
|
||||
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
|
||||
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-mugen-scroll": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz",
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"chai": "^4.3.7",
|
||||
"core-js": "^3.27.2",
|
||||
"dompurify": "^2.4.1",
|
||||
"dompurify": "^2.4.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.19.5",
|
||||
"hellojs": "^1.20.0",
|
||||
"inspectpack": "^4.7.1",
|
||||
"intro.js": "^6.0.0",
|
||||
"jquery": "^3.6.3",
|
||||
@@ -49,13 +49,13 @@
|
||||
"sass": "^1.34.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.19.1",
|
||||
"stopword": "^2.0.5",
|
||||
"stopword": "^2.0.7",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"svg-url-loader": "^7.1.1",
|
||||
"svgo": "^1.3.2",
|
||||
"svgo-loader": "^2.2.1",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.7.0",
|
||||
"validator": "^13.9.0",
|
||||
"vue": "^2.7.10",
|
||||
"vue-cli-plugin-storybook": "2.1.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<sub-canceled-modal v-if="isUserLoaded" />
|
||||
<bug-report-modal v-if="isUserLoaded" />
|
||||
<bug-report-success-modal v-if="isUserLoaded" />
|
||||
<external-link-modal />
|
||||
<birthday-modal />
|
||||
<snackbars />
|
||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||
@@ -175,6 +176,7 @@ import amazonPaymentsModal from '@/components/payments/amazonModal';
|
||||
import paymentsSuccessModal from '@/components/payments/successModal';
|
||||
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
||||
import subCanceledModal from '@/components/payments/canceledModal';
|
||||
import externalLinkModal from '@/components/externalLinkModal.vue';
|
||||
|
||||
import spellsMixin from '@/mixins/spells';
|
||||
import {
|
||||
@@ -210,6 +212,7 @@ export default {
|
||||
subCanceledModal,
|
||||
bugReportModal,
|
||||
bugReportSuccessModal,
|
||||
externalLinkModal,
|
||||
},
|
||||
mixins: [notifications, spellsMixin],
|
||||
data () {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 8.5 KiB |
@@ -19,8 +19,12 @@
|
||||
top: -16px !important;
|
||||
}
|
||||
|
||||
.Pet.Pet-FlyingPig-Veggie, .Pet.Pet-FlyingPig-Dessert, .Pet.Pet-FlyingPig-VirtualPet {
|
||||
top: -28px !important;
|
||||
$foolPets: Veggie, Dessert, VirtualPet, TeaShop;
|
||||
|
||||
@each $foolPet in $foolPets {
|
||||
.Pet.Pet-FlyingPig-#{$foolPet} {
|
||||
top: -28px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.Pet[class*="Virtual"] {
|
||||
|
||||
@@ -50,10 +50,7 @@ h3.markdown {
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue-10;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
color: $blue-10;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,19 +26,17 @@ a:not([href]), a:not([href]):hover {
|
||||
|
||||
a, a:not([href]):not([tabindex]) {
|
||||
cursor: pointer;
|
||||
color: $purple-300;
|
||||
|
||||
&.standard-link {
|
||||
color: $blue-10;
|
||||
&:hover, &:active, &:focus {
|
||||
text-decoration: underline;
|
||||
color: $purple-300;
|
||||
}
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&[disabled="disabled"] {
|
||||
color: $gray-300;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
}
|
||||
&[disabled="disabled"] {
|
||||
color: $gray-300;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.small-link {
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
Perk offset months:
|
||||
<strong>{{ hero.purchased.plan.consecutive.offset }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Perk month count:
|
||||
<strong>{{ hero.purchased.plan.perkMonthCount }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Next Mystic Hourglass:
|
||||
<strong>{{ nextHourglassDate }}</strong>
|
||||
@@ -149,7 +153,7 @@ export default {
|
||||
nextHourglassDate () {
|
||||
const currentPlanContext = getPlanContext(this.hero, new Date());
|
||||
|
||||
return currentPlanContext.nextHourglassDate.format('MMMM');
|
||||
return currentPlanContext.nextHourglassDate.format('MMMM YYYY');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -86,6 +86,13 @@
|
||||
>{{ $t('companyContribute') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://translate.habitica.com/"
|
||||
target="_blank"
|
||||
>{{ $t('translateHabitica') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Support -->
|
||||
@@ -101,6 +108,7 @@
|
||||
v-if="user"
|
||||
>
|
||||
<a
|
||||
href=""
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal()"
|
||||
>
|
||||
@@ -205,7 +213,7 @@
|
||||
</a>
|
||||
<a
|
||||
class="social-circle"
|
||||
href="https://twitter.com/habitica"
|
||||
href="https://twitter.com/habitica/"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
@@ -215,7 +223,7 @@
|
||||
</a>
|
||||
<a
|
||||
class="social-circle"
|
||||
href="https://www.facebook.com/Habitica"
|
||||
href="https://www.facebook.com/Habitica/"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
@@ -472,10 +480,6 @@ footer {
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:not([href]):not([class]):hover { // needed to make "report a bug"'s hover state correct
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
column-gap: 1.5rem;
|
||||
display: grid;
|
||||
@@ -675,11 +679,6 @@ h3 {
|
||||
|
||||
footer {
|
||||
padding: 24px 16px;
|
||||
a:not([href]):not([class]):hover { // needed to make "report a bug"'s hover state correct
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
column-gap: 1.5rem;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
@@ -719,10 +718,6 @@ h3 {
|
||||
@media (max-width: 1024px) and (min-width: 768px) {
|
||||
footer {
|
||||
padding: 24px 24px;
|
||||
a:not([href]):not([class]):hover { // needed to make "report a bug"'s hover state correct
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.desktop {
|
||||
@@ -815,7 +810,7 @@ export default {
|
||||
...mapState({ user: 'user.data' }),
|
||||
...mapState(['isUserLoaded']),
|
||||
getDataDisplayToolUrl () {
|
||||
const base = 'https://oldgods.net/habitrpg/habitrpg_user_data_display.html';
|
||||
const base = 'https://tools.habitica.com/';
|
||||
if (!this.user) return null;
|
||||
return `${base}?uuid=${this.user._id}`;
|
||||
},
|
||||
|
||||
@@ -244,7 +244,7 @@ export default {
|
||||
petClass () {
|
||||
if (some(
|
||||
this.currentEventList,
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'virtual',
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'teaShop',
|
||||
)) {
|
||||
return this.foolPet(this.member.items.currentPet);
|
||||
}
|
||||
|
||||
@@ -159,7 +159,6 @@ label {
|
||||
}
|
||||
|
||||
.cancel-link {
|
||||
color: $blue-10;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ label {
|
||||
}
|
||||
|
||||
.cancel-link {
|
||||
color: $blue-10;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
|
||||
@@ -322,6 +322,7 @@ import omit from 'lodash/omit';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
||||
import closeChallengeModal from './closeChallengeModal';
|
||||
import Column from '../tasks/column';
|
||||
@@ -358,7 +359,7 @@ export default {
|
||||
userLink,
|
||||
groupLink,
|
||||
},
|
||||
mixins: [challengeMemberSearchMixin, userStateMixin],
|
||||
mixins: [challengeMemberSearchMixin, externalLinks, userStateMixin],
|
||||
props: ['challengeId'],
|
||||
data () {
|
||||
return {
|
||||
@@ -414,6 +415,10 @@ export default {
|
||||
mounted () {
|
||||
if (!this.searchId) this.searchId = this.challengeId;
|
||||
if (!this.challenge._id) this.loadChallenge();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
async beforeRouteUpdate (to, from, next) {
|
||||
this.searchId = to.params.challengeId;
|
||||
|
||||
@@ -120,6 +120,7 @@ import { mapState } from '@/libs/store';
|
||||
import Sidebar from './sidebar';
|
||||
import ChallengeItem from './challengeItem';
|
||||
import challengeModal from './challengeModal';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
@@ -131,7 +132,7 @@ export default {
|
||||
challengeModal,
|
||||
MugenScroll,
|
||||
},
|
||||
mixins: [challengeUtilities],
|
||||
mixins: [challengeUtilities, externalLinks],
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
@@ -177,6 +178,10 @@ export default {
|
||||
section: this.$t('challenges'),
|
||||
});
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
updateSearch (eventData) {
|
||||
|
||||
@@ -81,6 +81,8 @@ import challengeModal from './challengeModal';
|
||||
import { mapState } from '@/libs/store';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import challengeItem from './challengeItem';
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
|
||||
@@ -92,6 +94,7 @@ export default {
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
props: ['group'],
|
||||
data () {
|
||||
return {
|
||||
@@ -118,6 +121,10 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
async loadChallenges () {
|
||||
|
||||
@@ -145,6 +145,7 @@ import Sidebar from './sidebar';
|
||||
import ChallengeItem from './challengeItem';
|
||||
import challengeModal from './challengeModal';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
@@ -156,7 +157,7 @@ export default {
|
||||
challengeModal,
|
||||
MugenScroll,
|
||||
},
|
||||
mixins: [challengeUtilities],
|
||||
mixins: [challengeUtilities, externalLinks],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
@@ -203,6 +204,10 @@ export default {
|
||||
section: this.$t('challenges'),
|
||||
});
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
updateSearch (eventData) {
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
}
|
||||
|
||||
a.cancel-link {
|
||||
color: $blue-10;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="external-link-modal"
|
||||
size="md"
|
||||
>
|
||||
<!-- HEADER -->
|
||||
<div slot="modal-header">
|
||||
<div
|
||||
class="modal-close"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
class="icon-close"
|
||||
v-html="icons.close"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="exclamation-container d-flex align-items-center justify-content-center">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-exclamation"
|
||||
v-html="icons.exclamation"
|
||||
></div>
|
||||
</div>
|
||||
<h2>
|
||||
{{ $t('leaveHabitica') }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- BODY -->
|
||||
<div
|
||||
class="row leave-warning-text"
|
||||
v-html="$t('leaveHabiticaText')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="skip-modal"
|
||||
>
|
||||
{{ $t('skipExternalLinkModal') }}
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div slot="modal-footer">
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary"
|
||||
@click="proceed()"
|
||||
>
|
||||
{{ $t('continue') }}
|
||||
</button>
|
||||
<div
|
||||
v-once
|
||||
class="close-link justify-content-center"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
#external-link-modal {
|
||||
&.modal {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.modal-md {
|
||||
max-width: 448px;
|
||||
min-width: 330px;
|
||||
margin: auto;
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
.icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
|
||||
& svg {
|
||||
fill: $yellow-1;
|
||||
opacity: 0.75;
|
||||
}
|
||||
& :hover {
|
||||
fill: $yellow-1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
justify-content: center;
|
||||
padding-top: 32px;
|
||||
padding-bottom: 0px;
|
||||
background: $yellow-100;
|
||||
border-top-right-radius: 8px;
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom: none;
|
||||
|
||||
.exclamation-container {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: $yellow-1;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.svg-exclamation {
|
||||
width: 8px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $yellow-1;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px 44px 20px 44px;
|
||||
background: $white;
|
||||
|
||||
.leave-warning-text {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
margin-top:24px;
|
||||
}
|
||||
|
||||
.skip-modal {
|
||||
color: $gray-100;
|
||||
font-size: 0.75rem;
|
||||
text-align: center;
|
||||
line-height: 1.33;
|
||||
margin-top: 16px;
|
||||
// padding-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background: $white;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
justify-content: center;
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
.close-link {
|
||||
color: $purple-300;
|
||||
line-height: 1.71;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
margin-top:16px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import exclamationIcon from '@/assets/svg/exclamation.svg';
|
||||
import closeIcon from '@/assets/svg/new-close.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: closeIcon,
|
||||
exclamation: exclamationIcon,
|
||||
}),
|
||||
url: '',
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:external-link', url => {
|
||||
this.url = url;
|
||||
this.$root.$emit('bv::show::modal', 'external-link-modal');
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:external-link');
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'external-link-modal');
|
||||
},
|
||||
proceed () {
|
||||
window.open(this.url, '_blank').focus();
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -87,6 +87,8 @@
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import autocomplete from '../chat/autoComplete';
|
||||
import communityGuidelines from './communityGuidelines';
|
||||
import chatMessage from '../chat/chatMessages';
|
||||
@@ -103,6 +105,7 @@ export default {
|
||||
communityGuidelines,
|
||||
chatMessage,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
props: ['label', 'group', 'placeholder'],
|
||||
data () {
|
||||
return {
|
||||
@@ -132,6 +135,10 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.textbox = this.$refs['user-entry'];
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
a:not([href]) {
|
||||
color: $blue-10;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -245,10 +245,6 @@
|
||||
text-align: center;
|
||||
|
||||
color: $gray-100;
|
||||
|
||||
a {
|
||||
color: $blue-10;
|
||||
}
|
||||
}
|
||||
|
||||
#quest-detail-modal {
|
||||
|
||||
@@ -377,11 +377,9 @@
|
||||
|
||||
.members-invited {
|
||||
min-height: 1rem;
|
||||
color: $blue-10;
|
||||
margin: 0;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: $blue-10;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,12 +340,13 @@
|
||||
<li>
|
||||
<a
|
||||
v-once
|
||||
href="https://oldgods.net/habitrpg/habitrpg_user_data_display.html"
|
||||
href="https://tools.habitica.com/"
|
||||
target="_blank"
|
||||
>{{ $t('dataDisplayTool') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href=""
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal()"
|
||||
>
|
||||
@@ -521,21 +522,6 @@
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
// formats the report a bug link to match the others
|
||||
a:not([href]) {
|
||||
&:not([role=button]) {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
a:not([href]):hover {
|
||||
&:not([role=button]) {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.tier1-icon, .tier2-icon {
|
||||
width: 11px;
|
||||
}
|
||||
@@ -759,6 +745,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { goToModForm } from '@/libs/modform';
|
||||
|
||||
@@ -835,22 +822,23 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
currentEvent: 'worldState.data.currentEvent',
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
questData () {
|
||||
if (!this.group.quest) return {};
|
||||
return quests.quests[this.group.quest.key];
|
||||
},
|
||||
imageURLs () {
|
||||
if (!this.currentEvent || !this.currentEvent.season) {
|
||||
const currentEvent = find(this.currentEventList, event => Boolean(event.season));
|
||||
if (!currentEvent) {
|
||||
return {
|
||||
background: 'url(/static/npc/normal/tavern_background.png)',
|
||||
npc: 'url(/static/npc/normal/tavern_npc.png)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: `url(/static/npc/${this.currentEvent.season}/tavern_background.png)`,
|
||||
npc: `url(/static/npc/${this.currentEvent.season}/tavern_npc.png)`,
|
||||
background: `url(/static/npc/${currentEvent.season}/tavern_background.png)`,
|
||||
npc: `url(/static/npc/${currentEvent.season}/tavern_npc.png)`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,14 @@
|
||||
:notification="notification"
|
||||
>
|
||||
<div slot="content">
|
||||
<div v-html="$t('invitedToParty', {party: notification.data.name})"></div>
|
||||
<div
|
||||
v-html="$t('invitedToPartyBy', {
|
||||
userId: notification.data.inviter,
|
||||
userName: invitingUser.auth ? invitingUser.auth.local.username : null,
|
||||
party: notification.data.name,
|
||||
})"
|
||||
>
|
||||
</div>
|
||||
<div class="notifications-buttons">
|
||||
<div
|
||||
class="btn btn-small btn-success"
|
||||
@@ -32,10 +39,31 @@ export default {
|
||||
components: {
|
||||
BaseNotification,
|
||||
},
|
||||
props: ['notification', 'canRemove'],
|
||||
props: {
|
||||
notification: {
|
||||
type: Object,
|
||||
default (data) {
|
||||
return data;
|
||||
},
|
||||
},
|
||||
canRemove: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
invitingUser: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
async mounted () {
|
||||
this.invitingUser = await this.$store.dispatch('members:fetchMember', {
|
||||
memberId: this.notification.data.inviter,
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
async accept () {
|
||||
const group = this.notification.data;
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
{{ $t('notifications') }}
|
||||
</h4>
|
||||
<a
|
||||
class="small-link standard-link"
|
||||
class="small-link"
|
||||
:disabled="notificationsCount === 0"
|
||||
@click="dismissAll"
|
||||
>{{ $t('dismissAll') }}</a>
|
||||
|
||||
@@ -879,7 +879,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.user.preferences.suppressModals.raisePet) {
|
||||
if (this.user.preferences.suppressModals.hatchPet) {
|
||||
this.hatchPet(pet);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,8 +171,9 @@ export default {
|
||||
getPetItemClass () {
|
||||
if (this.isOwned() && some(
|
||||
this.currentEventList,
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'virtual',
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'teaShop',
|
||||
)) {
|
||||
if (this.isSpecial()) return `Pet ${this.foolPet(this.item.key)}`;
|
||||
const petString = `${this.item.eggKey}-${this.item.key}`;
|
||||
return `Pet ${this.foolPet(petString)}`;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import renderWithMentions from '@/libs/renderWithMentions';
|
||||
import { mapState } from '@/libs/store';
|
||||
import userLink from '../userLink';
|
||||
@@ -150,6 +152,7 @@ export default {
|
||||
components: {
|
||||
userLink,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(value).fromNow();
|
||||
@@ -179,6 +182,10 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.$emit('message-card-mounted');
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
report () {
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
</button>
|
||||
<a
|
||||
v-once
|
||||
class="standard-link"
|
||||
@click="close()"
|
||||
>{{ $t('neverMind') }}</a>
|
||||
</div>
|
||||
|
||||
@@ -180,7 +180,6 @@
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
a:not([href]) {
|
||||
color: $blue-10;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
@@ -23,33 +23,7 @@
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>{{ $t('thirdPartyApps') }}</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://www.beeminder.com/habitica"
|
||||
>{{ $t('beeminder') }}</a>
|
||||
<br>
|
||||
{{ $t('beeminderDesc') }}
|
||||
</li>
|
||||
<li>
|
||||
<div v-html="$t('chatExtension')">
|
||||
</div>
|
||||
<span>{{ $t('chatExtensionDesc') }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="`https://oldgods.net/habitica/habitrpg_user_data_display.html?uuid=` + user._id"
|
||||
>{{ $t('dataDisplayTool') }}</a>
|
||||
<br>
|
||||
{{ $t('dataToolDesc') }}
|
||||
</li>
|
||||
<li>
|
||||
<div v-html="$t('otherExtensions')"></div>
|
||||
<span>{{ $t('otherDesc') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-html="$t('thirdPartyTools')"></p>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,38 @@
|
||||
{{ $t('settings') }}
|
||||
</h1>
|
||||
<div class="col-sm-6">
|
||||
<div class="sleep">
|
||||
<h5>{{ $t('pauseDailies') }}</h5>
|
||||
<h4>{{ $t('sleepDescription') }}</h4>
|
||||
<ul>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet1') }}
|
||||
</li>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet2') }}
|
||||
</li>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet3') }}
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
v-if="!user.preferences.sleep"
|
||||
v-once
|
||||
class="sleep btn btn-primary btn-block pause-button"
|
||||
@click="toggleSleep()"
|
||||
>
|
||||
{{ $t('pauseDailies') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="user.preferences.sleep"
|
||||
v-once
|
||||
class="btn btn-secondary btn-block pause-button"
|
||||
@click="toggleSleep()"
|
||||
>
|
||||
{{ $t('unpauseDailies') }}
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-horizontal">
|
||||
<h5>{{ $t('language') }}</h5>
|
||||
<select
|
||||
@@ -517,6 +549,10 @@
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.sleep {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -651,6 +687,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSleep () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
validateDisplayName: debounce(function checkName (displayName) {
|
||||
if (displayName.length <= 1 || displayName === this.user.profile.name) {
|
||||
this.displayNameIssues = [];
|
||||
|
||||
@@ -804,7 +804,7 @@ export default {
|
||||
return currentPlanContext.nextHourglassDate;
|
||||
},
|
||||
nextHourGlass () {
|
||||
const nextHourglassMonth = this.nextHourGlassDate.format('MMM');
|
||||
const nextHourglassMonth = this.nextHourGlassDate.format('MMM YYYY');
|
||||
|
||||
return nextHourglassMonth;
|
||||
},
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import _filter from 'lodash/filter';
|
||||
import _map from 'lodash/map';
|
||||
import _throttle from 'lodash/throttle';
|
||||
@@ -225,7 +226,7 @@ export default {
|
||||
user: 'user.data',
|
||||
userStats: 'user.data.stats',
|
||||
userItems: 'user.data.items',
|
||||
currentEvent: 'worldState.data.currentEvent',
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
market () {
|
||||
return shops.getMarketShop(this.user);
|
||||
@@ -292,15 +293,16 @@ export default {
|
||||
return Object.values(this.viewOptions).some(g => g.selected);
|
||||
},
|
||||
imageURLs () {
|
||||
if (!this.currentEvent || !this.currentEvent.season) {
|
||||
const currentEvent = find(this.currentEventList, event => Boolean(event.season));
|
||||
if (!currentEvent) {
|
||||
return {
|
||||
background: 'url(/static/npc/normal/market_background.png)',
|
||||
npc: 'url(/static/npc/normal/market_banner_npc.png)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: `url(/static/npc/${this.currentEvent.season}/market_background.png)`,
|
||||
npc: `url(/static/npc/${this.currentEvent.season}/market_banner_npc.png)`,
|
||||
background: `url(/static/npc/${currentEvent.season}/market_background.png)`,
|
||||
npc: `url(/static/npc/${currentEvent.season}/market_banner_npc.png)`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -397,6 +397,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import _filter from 'lodash/filter';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _throttle from 'lodash/throttle';
|
||||
@@ -512,7 +513,7 @@ export default {
|
||||
user: 'user.data',
|
||||
userStats: 'user.data.stats',
|
||||
userItems: 'user.data.items',
|
||||
currentEvent: 'worldState.data.currentEvent',
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
shop () {
|
||||
return shops.getQuestShop(this.user);
|
||||
@@ -536,15 +537,16 @@ export default {
|
||||
return Object.values(this.viewOptions).some(g => g.selected);
|
||||
},
|
||||
imageURLs () {
|
||||
if (!this.currentEvent || !this.currentEvent.season) {
|
||||
const currentEvent = find(this.currentEventList, event => Boolean(event.season));
|
||||
if (!currentEvent) {
|
||||
return {
|
||||
background: 'url(/static/npc/normal/quest_shop_background.png)',
|
||||
npc: 'url(/static/npc/normal/quest_shop_npc.png)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: `url(/static/npc/${this.currentEvent.season}/quest_shop_background.png)`,
|
||||
npc: `url(/static/npc/${this.currentEvent.season}/quest_shop_npc.png)`,
|
||||
background: `url(/static/npc/${currentEvent.season}/quest_shop_background.png)`,
|
||||
npc: `url(/static/npc/${currentEvent.season}/quest_shop_npc.png)`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="container-fluid">
|
||||
<h1>{{ $t('communityGuidelines') }}</h1>
|
||||
<hr>
|
||||
<p>{{ $t('lastUpdated') }} July 28, 2021</p>
|
||||
<p>{{ $t('lastUpdated') }} February 8, 2023</p>
|
||||
<h2 id="welcome">
|
||||
{{ $t('commGuideHeadingWelcome') }}
|
||||
</h2>
|
||||
@@ -21,6 +21,7 @@
|
||||
<p v-html="$t('commGuidePara016')"></p>
|
||||
<p v-html="$t('commGuidePara017')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList01F')"></li>
|
||||
<li v-html="$t('commGuideList01A')"></li>
|
||||
<li v-html="$t('commGuideList01B')"></li>
|
||||
<li v-html="$t('commGuideList01C')"></li>
|
||||
@@ -32,6 +33,7 @@
|
||||
<img src="~@/assets/images/community-guidelines/publicSpaces.png">
|
||||
</div>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList02N')"></li>
|
||||
<li v-html="$t('commGuideList02A')"></li>
|
||||
<li v-html="$t('commGuideList02B')"></li>
|
||||
<li v-html="$t('commGuideList02G')"></li>
|
||||
@@ -147,10 +149,9 @@
|
||||
<li>
|
||||
{{ $t('commGuideList10A') }}
|
||||
<ul>
|
||||
<li>{{ $t('commGuideList10A1') }}</li>
|
||||
<li v-html="$t('commGuideList10A1')"></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-html="$t('commGuideList10C')"></li>
|
||||
<li v-html="$t('commGuideList10D')"></li>
|
||||
<li v-html="$t('commGuideList10F')"></li>
|
||||
</ul>
|
||||
@@ -176,50 +177,53 @@
|
||||
<h2 id="meet-the-mods">
|
||||
{{ $t('commGuideHeadingMeet') }}
|
||||
</h2>
|
||||
<p v-html="$t('commGuidePara006')"></p>
|
||||
<p v-html="$t('commGuidePara007')"></p>
|
||||
<p v-html="$t('commGuidePara008')"></p>
|
||||
<p v-html="$t('commGuidePara009')"></p>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/staff.png">
|
||||
<div class="media-body">
|
||||
<ul>
|
||||
<li>{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'heyeilatan', realName: 'Natalie'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'CuriousMagpie'}) }})
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
|
||||
- Mobile Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'redphoenix', realName: 'Vicky'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'veeeeeee'}) }})
|
||||
- Co-Founder
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}
|
||||
- Art, Community Management, Many Hats
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}
|
||||
- Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Piyo', realName: 'Sara'}) }}
|
||||
- Mobile Designer
|
||||
</li>
|
||||
<li>{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}</li>
|
||||
<li>{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}</li>
|
||||
<li>{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}</li>
|
||||
<li>{{ $t('commGuideAKA', {habitName: 'Piyo', realName: 'Sara'}) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara010')"></p>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/moderators.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara011')"></p>
|
||||
<ul>
|
||||
<li>Dewines</li>
|
||||
<li>Nakonana</li>
|
||||
<li>Cantras</li>
|
||||
<li>Alys (LadyAlys {{ $t('commGuidePara011c') }})</li>
|
||||
<li>Fox_town</li>
|
||||
<li>MaybeSteveRogers</li>
|
||||
<li>shanaqui</li>
|
||||
<li>deilann (not yet pictured)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara012')"></p>
|
||||
<p v-html="$t('commGuidePara013')"></p>
|
||||
<p>
|
||||
{{ $t('commGuidePara014') }}<br>
|
||||
<em>
|
||||
Lemoness, lefnire, Slappybag, litenull, Shaner, Bobbyroberts99, wc8,
|
||||
Breadstrings, Megan, Blade, and Daniel the Bard
|
||||
Breadstrings, Megan, Blade, Daniel the Bard, deilann, shanaqui, Nakonana,
|
||||
Dewines, Alys, Fox_town, MaybeSteveRogers, and Cantras.
|
||||
|
||||
</em>
|
||||
</p>
|
||||
<h2 id="final">
|
||||
@@ -240,6 +244,7 @@
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara069')"></p>
|
||||
<ul class="list-2col list-unstyled">
|
||||
<li>Beffymaroo</li>
|
||||
<li>Breadstrings</li>
|
||||
<li>Draayder</li>
|
||||
<li>Kiwibot</li>
|
||||
|
||||
@@ -354,6 +354,9 @@
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~@/assets/scss/static.scss';
|
||||
#front .form-text a {
|
||||
color: $white !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -362,10 +365,6 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Varela+Round');
|
||||
|
||||
#front {
|
||||
.form-text a {
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -165,10 +165,6 @@ export default {
|
||||
question: 'pkQuestion7',
|
||||
answer: 'pkAnswer7',
|
||||
},
|
||||
{
|
||||
question: 'pkQuestion8',
|
||||
answer: 'pkAnswer8',
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
</span>
|
||||
<a
|
||||
v-if="assignedUsersCount > 1 && !showStatus"
|
||||
class="blue-10"
|
||||
@click="showStatus = !showStatus"
|
||||
>
|
||||
{{ $t('viewStatus') }}
|
||||
@@ -128,10 +127,6 @@
|
||||
padding-top: 0.25rem;
|
||||
z-index: 9;
|
||||
height: 24px;
|
||||
|
||||
.blue-10 {
|
||||
color: $blue-10;
|
||||
}
|
||||
}
|
||||
|
||||
.completion-row {
|
||||
|
||||
@@ -355,6 +355,7 @@ import Task from './task';
|
||||
import ClearCompletedTodos from './clearCompletedTodos';
|
||||
import buyMixin from '@/mixins/buy';
|
||||
import sync from '@/mixins/sync';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
import { mapState, mapActions, mapGetters } from '@/libs/store';
|
||||
import shopItem from '../shops/shopItem';
|
||||
import BuyQuestModal from '@/components/shops/quests/buyQuestModal.vue';
|
||||
@@ -384,7 +385,7 @@ export default {
|
||||
shopItem,
|
||||
draggable,
|
||||
},
|
||||
mixins: [buyMixin, notifications, sync],
|
||||
mixins: [buyMixin, notifications, sync, externalLinks],
|
||||
// @TODO Set default values for props
|
||||
// allows for better control of props values
|
||||
// allows for better control of where this component is called
|
||||
@@ -534,6 +535,10 @@ export default {
|
||||
if (this.activeFilter.label !== 'complete2') return;
|
||||
this.loadCompletedTodos();
|
||||
});
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('buyModal::boughtItem');
|
||||
|
||||
@@ -593,7 +593,6 @@
|
||||
a:not(.dropdown-item) {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $blue-10;
|
||||
}
|
||||
|
||||
.modal-dialog.modal-sm {
|
||||
|
||||
@@ -276,7 +276,6 @@
|
||||
a {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $blue-10;
|
||||
margin-top: 4px;
|
||||
|
||||
&:focus, &:hover, &:active {
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { mapState } from '@/libs/store';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
import scoreTask from '@/mixins/scoreTask';
|
||||
import sync from '@/mixins/sync';
|
||||
import Task from './task';
|
||||
@@ -93,7 +94,7 @@ export default {
|
||||
Task,
|
||||
LoadingSpinner,
|
||||
},
|
||||
mixins: [scoreTask, sync],
|
||||
mixins: [externalLinks, scoreTask, sync],
|
||||
props: {
|
||||
yesterDailies: {
|
||||
type: Array,
|
||||
@@ -108,6 +109,9 @@ export default {
|
||||
dueDate: moment().subtract(1, 'days'),
|
||||
};
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
tasksByType () {
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: $gray-400;
|
||||
color: $white !important;
|
||||
text-decoration: none !important;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding: 0.5rem;
|
||||
|
||||
@@ -182,7 +182,6 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -296,10 +295,6 @@
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.challenge-link, .user-link {
|
||||
color: $blue-10 !important;
|
||||
}
|
||||
|
||||
.entry-action {
|
||||
b {
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -759,6 +759,7 @@ import challenge from '@/assets/svg/challenge.svg';
|
||||
import member from '@/assets/svg/member-icon.svg';
|
||||
import staff from '@/assets/svg/tier-staff.svg';
|
||||
import error404 from '../404';
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
import { userCustomStateMixin } from '../../mixins/userState';
|
||||
// @TODO: EMAILS.COMMUNITY_MANAGER_EMAIL
|
||||
const COMMUNITY_MANAGER_EMAIL = 'admin@habitica.com';
|
||||
@@ -772,7 +773,7 @@ export default {
|
||||
profileStats,
|
||||
error404,
|
||||
},
|
||||
mixins: [userCustomStateMixin('userLoggedIn')],
|
||||
mixins: [externalLinks, userCustomStateMixin('userLoggedIn')],
|
||||
props: ['userId', 'startingPage'],
|
||||
data () {
|
||||
return {
|
||||
@@ -862,8 +863,12 @@ export default {
|
||||
mounted () {
|
||||
this.loadUser();
|
||||
this.oldTitle = this.$store.state.title;
|
||||
this.handleExternalLinks();
|
||||
this.selectPage(this.startingPage);
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.oldTitle) {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
|
||||
@@ -33,9 +33,15 @@ setUpLogging();
|
||||
setupAnalytics(); // just create queues for analytics, no scripts loaded at this time
|
||||
const store = getStore();
|
||||
|
||||
export default new Vue({
|
||||
const vueInstance = new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
store,
|
||||
render: h => h(AppComponent),
|
||||
});
|
||||
|
||||
export default vueInstance;
|
||||
|
||||
window.externalLink = url => {
|
||||
vueInstance.$root.$emit('habitica:external-link', url);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import some from 'lodash/some';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
handleExternalLinks () {
|
||||
const { TRUSTED_DOMAINS } = process.env;
|
||||
const allLinks = document.getElementsByTagName('a');
|
||||
|
||||
for (let i = 0; i < allLinks.length; i += 1) {
|
||||
const link = allLinks[i];
|
||||
let domainIndex = link.href.indexOf('www');
|
||||
if (domainIndex !== -1 && domainIndex < 9) {
|
||||
domainIndex += 4;
|
||||
} else {
|
||||
domainIndex = link.href.indexOf('//') + 2;
|
||||
}
|
||||
|
||||
if ((link.classList.value.indexOf('external-link') === -1)
|
||||
&& domainIndex !== 1
|
||||
&& !some(TRUSTED_DOMAINS.split(','), domain => link.href.indexOf(domain) === domainIndex)) {
|
||||
link.classList.add('external-link');
|
||||
link.addEventListener('click', e => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
window.externalLink(link.href);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -40,15 +40,15 @@ export default {
|
||||
'Dragon',
|
||||
'Cactus',
|
||||
];
|
||||
if (!pet) return 'Pet-Cactus-Virtual';
|
||||
if (!pet) return 'Pet-TigerCub-TeaShop';
|
||||
if (SPECIAL_PETS.indexOf(pet) !== -1) {
|
||||
return 'Pet-Wolf-Virtual';
|
||||
return 'Pet-Dragon-TeaShop';
|
||||
}
|
||||
const species = pet.slice(0, pet.indexOf('-'));
|
||||
if (includes(BASE_PETS, species)) {
|
||||
return `Pet-${species}-Virtual`;
|
||||
return `Pet-${species}-TeaShop`;
|
||||
}
|
||||
return 'Pet-Fox-Virtual';
|
||||
return 'Pet-BearCub-TeaShop';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -272,6 +272,11 @@ const router = new VueRouter({
|
||||
name: 'transactions',
|
||||
path: 'transactions',
|
||||
component: Transactions,
|
||||
meta: {
|
||||
privilegeNeeded: [
|
||||
'userSupport',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'notifications',
|
||||
@@ -316,11 +321,6 @@ const router = new VueRouter({
|
||||
{
|
||||
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
// Commenting out merch page see
|
||||
// https://github.com/HabitRPG/habitica/issues/12039
|
||||
// {
|
||||
// name: 'merch', path: 'merch', component: MerchPage, meta: { requiresLogin: false },
|
||||
// },
|
||||
{
|
||||
name: 'news', path: 'new-stuff', component: NewsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// import omit from 'lodash/omit';
|
||||
// import findIndex from 'lodash/findIndex';
|
||||
|
||||
const apiv4Prefix = '/api/v4';
|
||||
|
||||
export async function getGroupMembers (store, payload) {
|
||||
@@ -117,38 +114,3 @@ export async function getPurchaseHistory (store, payload) {
|
||||
const response = await axios.get(`${apiv4Prefix}/members/${payload.memberId}/purchase-history`);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
// export async function selectMember (uid) {
|
||||
// let memberIsReady = _checkIfMemberIsReady(members[uid]);
|
||||
//
|
||||
// if (memberIsReady) {
|
||||
// _prepareMember(members[uid], self);
|
||||
// return
|
||||
// } else {
|
||||
// fetchMember(uid)
|
||||
// .then(function (response) {
|
||||
// var member = response.data.data;
|
||||
// addToMembersList(member); // lazy load for later
|
||||
// _prepareMember(member, self);
|
||||
// deferred.resolve();
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// function addToMembersList (member) {
|
||||
// if (member._id) {
|
||||
// members[member._id] = member;
|
||||
// }
|
||||
// }
|
||||
|
||||
// function _checkIfMemberIsReady (member) {
|
||||
// return member && member.items && member.items.weapon;
|
||||
// }
|
||||
//
|
||||
// function _prepareMember(member, self) {
|
||||
// self.selectedMember = members[member._id];
|
||||
// }
|
||||
//
|
||||
// $rootScope.$on('userUpdated', function(event, user){
|
||||
// addToMembersList(user);
|
||||
// })
|
||||
|
||||
@@ -27,6 +27,7 @@ const envVars = [
|
||||
'APPLE_AUTH_CLIENT_ID',
|
||||
'AMPLITUDE_KEY',
|
||||
'LOGGLY_CLIENT_TOKEN',
|
||||
'TRUSTED_DOMAINS',
|
||||
// TODO necessary? if yes how not to mess up with vue cli? 'NODE_ENV'
|
||||
];
|
||||
|
||||
|
||||
@@ -140,9 +140,12 @@
|
||||
"achievementWoodlandWizardText": "لقد فقس جميع الألوان القياسية لمخلوقات الغابة: الغرير، الدب، الغزال، الثعلب، الضفدع، القنفذ، البومة، الأفعى، السنجاب والشجيرة!",
|
||||
"achievementWoodlandWizardModalText": "لقد جمعت كل حيوانات الغابة الأليفة!",
|
||||
"achievementPolarPro": "المحترف القطبي",
|
||||
"achievementPolarProText": "فقست جميع الحيوانات الأليفة القطبية: الدب ، الثعلب ، البطريق ، الحوت ، والذئب!",
|
||||
"achievementPolarProText": "فقست جميع الحيوانات الأليفة القطبية في الألوان القياسية: الدب ، الثعلب ، البطريق ، الحوت ، والذئب!",
|
||||
"achievementPolarProModalText": "لقد جمعت كل الحيوانات الأليفة القطبية!",
|
||||
"achievementBoneToPick": "جامع العظام",
|
||||
"achievementBoneToPickText": "فقست جميع الحيوانات الأليفة الهيكلية المغامرة والكلاسيكية!",
|
||||
"achievementBoneToPickModalText": "لقد جمعت كل الحيوانات الأليفة الهيكلية المغامرة والكلاسيكية!"
|
||||
"achievementBoneToPickModalText": "لقد جمعت كل الحيوانات الأليفة الهيكلية المغامرة والكلاسيكية!",
|
||||
"achievementPlantParent": "والد النبات",
|
||||
"achievementPlantParentText": "فقست جميع الحيوانات الأليفة النباتية في الألوان القياسية: الصبار والتريلنج!",
|
||||
"achievementPlantParentModalText": "لقد جمعت كل الحيوانات الأليفة النباتية!"
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@
|
||||
"backgroundScribesWorkshopNotes": "Write your next great scroll in a Scribe's Workshop.",
|
||||
"backgrounds022019": "مجموعة 57: تم إصدارها في فبراير 2019",
|
||||
"backgroundBirthdayPartyText": "حفلة عيد ميلاد",
|
||||
"backgrounds012020": "مجموعة 68: تم طرحه في يناير 2020",
|
||||
"backgrounds012020": "المجموعة 68: صدرت في يناير 2020",
|
||||
"backgroundMedievalKitchenText": "مطبخ القرون الوسطى",
|
||||
"backgroundMedievalKitchenNotes": "اطبخ العاصفة في مطبخ القرون الوسطى.",
|
||||
"backgroundBirthdayPartyNotes": "احتفل بعيد ميلاد ال Habitican المفضل لديك.",
|
||||
@@ -448,5 +448,219 @@
|
||||
"backgroundDojoNotes": "تعلم حركات جديدة في دوجو.",
|
||||
"backgroundSchoolOfFishText": "سرب من الأسماك",
|
||||
"backgrounds072019": "المجموعة 62: صدرت في يوليو 2019",
|
||||
"backgroundParkWithStatueNotes": "اتبع مسارًا تصطف على جانبيه الأزهار عبر حديقة بها تمثال."
|
||||
"backgroundParkWithStatueNotes": "اتبع مسارًا تصطف على جانبيه الأزهار عبر حديقة بها تمثال.",
|
||||
"backgroundFlyingOverTropicalIslandsText": "التحليق فوق الجزر الاستوائية",
|
||||
"backgroundFlyingOverTropicalIslandsNotes": "دع المنظر يذهلك وأنت تحلق فوق الجزر الاستوائية.",
|
||||
"backgrounds082019": "المجموعة 63: صدرت في أغسطس 2019",
|
||||
"backgroundLakeWithFloatingLanternsNotes": "شاهد النجوم من بحيرة احتفالية عليها فوانيس عائمة.",
|
||||
"backgroundLakeWithFloatingLanternsText": "بحيرة عليها فوانيس عائمة",
|
||||
"backgroundTreehouseNotes": "استرخ بمفردك في منزل الشجرة الخاص بك.",
|
||||
"backgrounds092019": "المجموعة 64: صدرت في سبتمبر 2019",
|
||||
"backgroundAutumnFlowerGardenText": "حديقة الزهور الخريفية",
|
||||
"backgroundInAnAncientTombText": "قبر قديم",
|
||||
"backgroundAmidAncientRuinsNotes": "قف بوقار لماضٍ غامض بين الآثار القديمة.",
|
||||
"backgroundGiantDandelionsText": "الهندباء العملاقة",
|
||||
"backgroundAmongGiantAnemonesText": "بين شقائق النعمان العملاقة",
|
||||
"backgroundInAnAncientTombNotes": "واجه الأسرار من قبر قديم.",
|
||||
"backgroundAutumnFlowerGardenNotes": "تشمس في دفء حديقة الزهور الخريفية.",
|
||||
"backgroundTreehouseText": "منزل الشجرة",
|
||||
"backgroundAmongGiantAnemonesNotes": "استكشف الشعاب المرجانية بينما أنت محمي\\ة من الحيوانات المفترسة البحرية بين شقائق النعمان العملاقة.",
|
||||
"backgroundAmidAncientRuinsText": "بين الأطلال القديمة",
|
||||
"backgroundGiantDandelionsNotes": "اقضِ بعض الوقت بين الهندباء العملاقة.",
|
||||
"backgroundInAClassroomText": "صف",
|
||||
"backgroundInAClassroomNotes": "اكتسب المعرفة من معلميك في صف.",
|
||||
"backgrounds102019": "المجموعة 65: صدرت في اكتوبر 2019",
|
||||
"backgroundFoggyMoorText": "مستنقع ضبابي",
|
||||
"backgroundFoggyMoorNotes": "كن حذرًا أثناء عبور المستنقع الضبابي.",
|
||||
"backgroundPumpkinCarriageText": "عربة اليقطين",
|
||||
"backgroundPumpkinCarriageNotes": "اركب في عربة اليقطين المسحورة قبل وصول منتصف الليل.",
|
||||
"backgroundMonsterMakersWorkshopText": "ورشة عمل صانع الوحش",
|
||||
"backgroundMonsterMakersWorkshopNotes": "تجربة مع علوم مشوهة في ورشة عمل صانع الوحش.",
|
||||
"backgrounds112019": "المجموعة 66: صدرت في نوفمبر 2019",
|
||||
"backgroundFarmersMarketText": "سوق المزارعين",
|
||||
"backgroundFarmersMarketNotes": "تسوق للحصول على الطعام الطازج في سوق المزارعين.",
|
||||
"backgroundFlyingInAThunderstormText": "عاصفة رعدية مضطربة",
|
||||
"backgroundFlyingInAThunderstormNotes": "تابع عاصفة رعدية مضطربة إذا كنت تجرؤ.",
|
||||
"backgroundPotionShopNotes": "اعثر على إكسير لأي مرض في متجر الجرعات السحرية.",
|
||||
"backgroundPotionShopText": "متجر الجرعات السحرية",
|
||||
"backgrounds122019": "المجموعة 67: صدرت في ديسمبر 2019",
|
||||
"backgroundWinterNocturneText": "ليلة شتاء",
|
||||
"backgroundWinterNocturneNotes": "تشمس في ضوء النجوم في ليلة شتاء.",
|
||||
"backgroundElegantBallroomText": "قاعة رقص أنيقة",
|
||||
"backgroundDesertWithSnowText": "صحراء ثلجية",
|
||||
"backgrounds022020": "المجموعة 69: صدرت في فبراير 2020",
|
||||
"backgroundHallOfHeroesText": "قاعة الأبطال",
|
||||
"backgrounds032020": "المجموعة 70: صدرت في مارس 2020",
|
||||
"backgroundButterflyGardenText": "حديقة الفراشات",
|
||||
"backgroundSnowglobeNotes": "هز كرة ثلجية واستقر في صورة مصغرة لمنظر طبيعي شتوي.",
|
||||
"backgroundAmongGiantFlowersText": "بين الزهور العملاقة",
|
||||
"backgroundHallOfHeroesNotes": "اقترب من قاعة الأبطال بتقدير وتقديس.",
|
||||
"backgroundDesertWithSnowNotes": "شاهد الجمال النادر والهادئ للصحراء الثلجية.",
|
||||
"backgroundTeaPartyNotes": "شارك في حفلة شاي فاخرة.",
|
||||
"backgroundAnimalCloudsText": "غيوم على شكل حيوانات",
|
||||
"backgrounds042020": "المجموعة 71: صدرت في أبريل 2020",
|
||||
"backgroundAnimalCloudsNotes": "مارس خيالك بالعثور على أشكال حيوانات في الغيوم.",
|
||||
"backgroundHolidayMarketText": "سوق العطلات",
|
||||
"backgroundTeaPartyText": "حفلة شاي",
|
||||
"backgroundElegantBallroomNotes": "ارقص طوال الليل في قاعة رقص أنيقة.",
|
||||
"backgroundHolidayMarketNotes": "اعثر على الهدايا والزخارف المثالية في سوق العطلات.",
|
||||
"backgroundSnowglobeText": "كرة الثلج",
|
||||
"backgroundSucculentGardenNotes": "قدّر جمال حديقة النباتات النضرة.",
|
||||
"backgroundButterflyGardenNotes": "احتفل مع الملقحات في حديقة الفراشات.",
|
||||
"backgroundSucculentGardenText": "حديقة النباتات النضرة",
|
||||
"backgroundHolidayWreathText": "إكليل الصنوبر",
|
||||
"backgroundHolidayWreathNotes": "تزيين صورتك الرمزية مع إكليل عطر من الصنوبر.",
|
||||
"backgroundAmongGiantFlowersNotes": "اقض بعض الوقت بين الزهور العملاقة.",
|
||||
"backgroundHotAirBalloonText": "منطاد الهواء الساخن",
|
||||
"backgroundRelaxationRiverText": "نهر الاسترخاء",
|
||||
"backgroundHotAirBalloonNotes": "احلق فوق الأرض في منطاد الهواء الساخن.",
|
||||
"backgroundStrawberryPatchText": "حقل الفراولة",
|
||||
"backgroundHeatherFieldNotes": "استمتع برائحة حقل الخلنج.",
|
||||
"backgroundHabitCityRooftopsText": "أسطح المنازل في مدينة حابيت",
|
||||
"backgroundHabitCityRooftopsNotes": "خذ قفزات جريئة بين أسطح المنازل في مدينة حابيت.",
|
||||
"backgroundHeatherFieldText": "حقل الخلنج",
|
||||
"backgroundRainyBarnyardNotes": "تجول في مزرعة ممطرة.",
|
||||
"backgroundRainyBarnyardText": "مزرعة ممطرة",
|
||||
"backgrounds052020": "المجموعة 72: صدرت في مايو 2020",
|
||||
"backgroundStrawberryPatchNotes": "جمع التوت الطازج من حقل الفراولة.",
|
||||
"backgrounds062020": "المجموعة 73: صدرت في يونيو 2020",
|
||||
"backgroundRelaxationRiverNotes": "انجرف ببطء على طول نهر الاسترخاء.",
|
||||
"backgroundSaltLakeText": "بحيرة مالحة",
|
||||
"backgroundVikingShipText": "سفينة فايكنغ",
|
||||
"backgroundSaltLakeNotes": "شاهد التموجات الحمراء المذهلة لبحيرة مالحة.",
|
||||
"backgroundBeachCabanaNotes": "استرخِ في ظل كوخ الشاطئ.",
|
||||
"backgroundSwimmingAmongJellyfishText": "السباحة بين قناديل البحر",
|
||||
"backgrounds072020": "المجموعة 74: صدرت في يوليو 2020",
|
||||
"backgroundBeachCabanaText": "كوخ الشاطئ",
|
||||
"backgroundVikingShipNotes": "أبحر على متن سفينة فايكنغ بحثًا عن المغامرة.",
|
||||
"backgrounds082020": "المجموعة 75: صدرت في أغسطس 2020",
|
||||
"backgroundSwimmingAmongJellyfishNotes": "اشعر بالجمال والخطر وأنت تسبح بين قناديل البحر.",
|
||||
"backgroundUnderwaterRuinsText": "أطلال تحت الماء",
|
||||
"backgroundCampingOutNotes": "استمتع بالهواء الطلق أثناء رحلة التخييم.",
|
||||
"backgroundUnderwaterRuinsNotes": "اِسْتَكْشِفْ الأطلال تحت الماء التي غرقت منذ فترة طويلة.",
|
||||
"backgroundCampingOutText": "رحلة تخييم",
|
||||
"backgroundProductivityPlazaText": "ساحة الإنتاجية",
|
||||
"backgroundJungleCanopyText": "رؤوس الأشجار الاستوائية",
|
||||
"backgroundJungleCanopyNotes": "تشمس في الروعة الساخنة في رؤوس الأشجار الاستوائية.",
|
||||
"backgroundProductivityPlazaNotes": "قم بنزهة ملهمة في ساحة الإنتاجية في مدينة حابيت.",
|
||||
"backgroundFlyingOverAnAutumnForestNotes": "انظر إلى الألوان الرائعة أسفلك أثناء التحليق فوق الغابة الخريفية.",
|
||||
"backgroundFlyingOverAnAutumnForestText": "التحليق فوق الغابة الخريفية",
|
||||
"backgrounds092020": "المجموعة 76: صدرت في سبتمبر 2020",
|
||||
"backgroundGiantAutumnLeafNotes": "اجثم على ورقة عملاقة قبل أن تسقط.",
|
||||
"backgroundGiantAutumnLeafText": "ورقة عملاقة",
|
||||
"backgroundHerdingSheepInAutumnNotes": "اختلط بالقطيع من الأغنام.",
|
||||
"backgroundHerdingSheepInAutumnText": "قطيع من الأغنام",
|
||||
"backgrounds102020": "المجموعة 77: صدرت في اكتوبر 2020",
|
||||
"backgroundGingerbreadHouseText": "بيت كعك الزنجبيل",
|
||||
"backgroundSpookyScarecrowFieldText": "حقل الفزاعة المخيفة",
|
||||
"backgroundRiverOfLavaText": "نهر من الحمم البركانية",
|
||||
"backgroundMysticalObservatoryText": "مرصد باطني",
|
||||
"backgroundCrescentMoonNotes": "قم بعمل الأحلام وأنت جالس على الهلال.",
|
||||
"backgroundHauntedForestText": "غابة مسكونة",
|
||||
"backgroundRiverOfLavaNotes": "تحدى الحمل الحراري وتجول على طول نهر الحمم البركانية.",
|
||||
"backgroundHauntedForestNotes": "حاول ألا تضيع في الغابة المسكونة.",
|
||||
"backgroundCrescentMoonText": "هلال",
|
||||
"backgroundSpookyScarecrowFieldNotes": "أثبت أنك أكثر شجاعة من الطائر بالذهاب إلى حقل الفزاعة المخيفة.",
|
||||
"backgrounds112020": "المجموعة 78: صدرت في نوفمبر 2020",
|
||||
"backgroundMysticalObservatoryNotes": "اقرأ مصيرك في النجوم من المرصد الباطني.",
|
||||
"backgroundRestingInTheInnNotes": "اعمل براحة وأمان في غرفتك أثناء الراحة في النزل.",
|
||||
"backgroundRestingInTheInnText": "الراحة في النزل",
|
||||
"backgrounds122020": "المجموعة 79: صدرت في ديسمبر 2020",
|
||||
"backgroundGingerbreadHouseNotes": "تحيط علما بالمشاهد والروائح و(إن كنت تجرؤ) النكهات بيت كعك الزنجبيل.",
|
||||
"backgroundIcicleBridgeNotes": "اعبر الجسر الجليدي بحذر.",
|
||||
"backgroundIcicleBridgeText": "جسر جليدي",
|
||||
"backgroundInsideAnOrnamentText": "داخل حلية العطلة",
|
||||
"backgroundWintryCastleNotes": "انظر إلى القلعة الشتوية التي تقع داخل الضباب البارد.",
|
||||
"backgroundHolidayHearthText": "مدفأة احتفالية",
|
||||
"backgrounds022021": "المجموعة 81: صدرت في فبراير 2021",
|
||||
"backgroundFlyingOverGlacierText": "تحلق فوق نهر جليدي",
|
||||
"backgrounds012021": "المجموعة 80: صدرت في يناير 2021",
|
||||
"backgroundWintryCastleText": "قلعة شتوية",
|
||||
"backgroundHotSpringNotes": "تخلص من همومك عن طريق الراحة في ينبوع حار.",
|
||||
"backgroundHotSpringText": "ينبوع حار",
|
||||
"backgroundHolidayHearthNotes": "استرخ ، دفئ نفسكَ وجفف نفسك بجانب مدفأة احتفالية.",
|
||||
"backgroundInsideAnOrnamentNotes": "دع روح العطلة الخاصة بك تتألق من داخل حلية العطلة.",
|
||||
"backgroundFlyingOverGlacierNotes": "شاهد المشهد المهيب وأنت تحلق فوق نهر جليدي.",
|
||||
"backgroundHeartShapedBubblesText": "فقاعات على شكل قلب",
|
||||
"backgroundHeartShapedBubblesNotes": "اطف بمرح بين الفقاعات على شكل قلب.",
|
||||
"backgrounds032021": "المجموعة 82: صدرت في مارس 2021",
|
||||
"backgroundThroneRoomText": "غرفة العرش",
|
||||
"backgroundThroneRoomNotes": "امنح جمهورًا في غرفة العرش الفاخرة الخاصة بك.",
|
||||
"backgroundInTheArmoryText": "في مستودع الأسلحة",
|
||||
"backgroundSplashInAPuddleNotes": "استمتع بآثار العاصفة بالرش في بركة.",
|
||||
"backgroundSpringThawText": "ذوبـان الثلوج في فصل الربيع",
|
||||
"backgroundSpringThawNotes": "شاهد الشتاء يختفي مع ذوبان الثلوج في فصل الربيع.",
|
||||
"backgrounds042021": "المجموعة 83: صدرت في أبريل 2021",
|
||||
"backgroundInTheArmoryNotes": "جهز نفسك في مستودع الأسلحة.",
|
||||
"backgroundSplashInAPuddleText": "الرش في بركة",
|
||||
"backgroundAmongCattailsText": "بين القصب",
|
||||
"backgroundElegantGardenText": "حديقة أنيقة",
|
||||
"backgroundAmongCattailsNotes": "انظر إلى الحياة البرية في الأراضي الرطبة بين القصب.",
|
||||
"backgroundCottageConstructionNotes": "ساعد في, أو على الأقل الإشراف على كوخ قيد الإنشاء.",
|
||||
"backgroundCottageConstructionText": "كوخ قيد الانشاء",
|
||||
"backgroundForestedLakeshoreText": "شاطئ البحيرة الحرجي",
|
||||
"backgroundDragonsLairText": "عرين التنين",
|
||||
"backgroundWaterMillNotes": "شاهد عجلة الطاحونة المائية تدور.",
|
||||
"backgroundGhostShipText": "سفينة الأشباح",
|
||||
"backgrounds062021": "المجموعة 85: صدرت في يونيو 2021",
|
||||
"backgroundWindmillsText": "طواحين الهواء",
|
||||
"backgroundAfternoonPicnicNotes": "استمتع بنزهة بعد الظهر بمفردك أو مع حيوانك الأليف.",
|
||||
"backgroundClotheslineText": "حبل الغسيل",
|
||||
"backgroundUnderwaterAmongKoiNotes": "أبهر وانبهر بالمخلوقات المتلألئة تحت الماء بين أسماك كوي.",
|
||||
"backgrounds052021": "المجموعة 84: صدرت في مايو 2021",
|
||||
"backgrounds072021": "المجموعة 86: صدرت في يوليو 2021",
|
||||
"backgroundGhostShipNotes": "أثبت صحة القصص والأساطير عندما تصعد على متن سفينة الأشباح.",
|
||||
"backgroundElegantGardenNotes": "تجول على طول المسارات المشذبة الجميلة لحديقة أنيقة.",
|
||||
"backgroundForestedLakeshoreNotes": "كن موضع حسد فريقك من خلال موقعك المتميز على شاطئ البحيرة الحرجي.",
|
||||
"backgroundDragonsLairNotes": "حاول ألا تزعج ساكن عرين التنين.",
|
||||
"backgroundAfternoonPicnicText": "نزهة بعد الظهر",
|
||||
"backgroundWaterMillText": "طاحونة مائية",
|
||||
"backgroundClotheslineNotes": "استرخِ بينما تجف الملابس على حبل الغسيل.",
|
||||
"backgroundWindmillsNotes": "استعد لمحاربة أعداء غير مرئيين في طواحين الهواء.",
|
||||
"backgroundUnderwaterAmongKoiText": "تحت الماء بين أسماك كوي",
|
||||
"backgroundRagingRiverText": "نهر هائج",
|
||||
"backgroundRagingRiverNotes": "قف وسط التيار العظيم لنهر هائج.",
|
||||
"backgrounds082021": "المجموعة 87: صدرت في أغسطس 2021",
|
||||
"backgroundStoneTowerText": "برج حجري",
|
||||
"backgroundStoneTowerNotes": "انظر من حاجز البرج الحجري إلى آخر.",
|
||||
"backgroundRopeBridgeText": "جسر الحبل",
|
||||
"backgroundDaytimeMistyForestText": "غابة ضبابية",
|
||||
"backgroundRopeBridgeNotes": "أظهر للمشككين أن جسر الحبل هذا آمن تمامًا.",
|
||||
"backgrounds092021": "المجموعة 88: صدرت في سبتمبر 2021",
|
||||
"backgroundVineyardText": "مزرعة العنب",
|
||||
"backgroundAutumnPoplarsText": "غابة الحور الخريفية",
|
||||
"backgroundDaytimeMistyForestNotes": "تشمس بأشعة الضوء المشرقة من خلال الغابة الضبابية.",
|
||||
"backgroundVineyardNotes": "استكشف مساحة من مزرعة العنب المثمرة.",
|
||||
"backgroundAutumnLakeshoreText": "شاطئ البحيرة الخريفية",
|
||||
"backgrounds102021": "المجموعة 89: صدرت في اكتوبر 2021",
|
||||
"backgroundAutumnLakeshoreNotes": "توقف عند شاطئ البحيرة الخريفية لتقدير انعكاس الغابة على الماء.",
|
||||
"backgroundAutumnPoplarsNotes": "استمتع بالدرجات اللامعة من اللون البني والذهبي في غابة الحور الخريفية.",
|
||||
"backgrounds122021": "المجموعة 91: صدرت في ديسمبر 2021",
|
||||
"backgrounds112021": "المجموعة 90: صدرت في نوفمبر 2021",
|
||||
"backgroundFortuneTellersShopText": "محل العرافة",
|
||||
"backgroundFortuneTellersShopNotes": "ابحث عن تلميحات مغرية لمستقبلك في محل العرافة.",
|
||||
"backgroundInsideAPotionBottleText": "داخل زجاجة الجرعة السحرية",
|
||||
"backgroundInsideAPotionBottleNotes": "انظر عبر الزجاج بينما تأمل في الإنقاذ من داخل زجاجة الجرعة السحرية.",
|
||||
"backgroundSpiralStaircaseText": "درج حلزوني",
|
||||
"backgroundSpiralStaircaseNotes": "اصعد ، انزل ، واذهب حول الدرج الحلزوني.",
|
||||
"backgroundCrypticCandlesText": "شموع خفية",
|
||||
"backgroundCrypticCandlesNotes": "استدع قوى غامضة بين الشموع الخفية.",
|
||||
"backgroundHauntedPhotoText": "صورة مسكونة",
|
||||
"backgroundHauntedPhotoNotes": "تجد نفسك محاصرًا في عالم أحادي اللون لصورة مسكونة.",
|
||||
"backgroundUndeadHandsText": "أيدي الزومبي",
|
||||
"backgroundUndeadHandsNotes": "حاول الهروب من براثن أيدي الزومبي.",
|
||||
"backgrounds022022": "المجموعة 93: صدرت في فبراير 2022",
|
||||
"backgroundFrozenPolarWatersText": "المياه القطبية المجمدة",
|
||||
"backgroundFrozenPolarWatersNotes": "استكشف المياه القطبية المجمدة.",
|
||||
"backgroundWinterCanyonText": "وادي شتوي",
|
||||
"backgroundWinterCanyonNotes": "اذهب في مغامرة في وادي شتوي!",
|
||||
"backgroundIcePalaceText": "قصر الجليد",
|
||||
"backgrounds012022": "المجموعة 92: صدرت في يناير 2022",
|
||||
"backgroundSnowyFarmText": "مزرعة ثلجية",
|
||||
"backgroundMeteorShowerText": "دش النيزك",
|
||||
"backgroundMeteorShowerNotes": "شاهد العرض الليلي المبهر لدش النيزك.",
|
||||
"backgroundPalmTreeWithFairyLightsText": "شجرة نخيل مع أضواء زخرفية",
|
||||
"backgroundSnowyFarmNotes": "تأكد من أن الجميع آمنون ودافئون في مزرعتك الثلجية.",
|
||||
"backgroundIcePalaceNotes": "احكم في قصر الجليد.",
|
||||
"backgroundPalmTreeWithFairyLightsNotes": "قف بجانب شجرة نخيل ملفوفة بأضواء زخرفية."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
|
||||
"tavernCommunityGuidelinesPlaceholder": "Friendly reminder: this is an all-ages chat, so please keep content and language appropriate! Consult the Community Guidelines in the sidebar if you have questions.",
|
||||
"lastUpdated": "آخر تحديث:",
|
||||
"commGuideHeadingWelcome": "أهلاً وسهلاً بك في Habitica!",
|
||||
@@ -7,7 +6,7 @@
|
||||
"commGuidePara002": "To help keep everyone safe, happy, and productive in the community, we do have some guidelines. We have carefully crafted them to make them as friendly and easy-to-read as possible. Please take the time to read them before you start chatting.",
|
||||
"commGuidePara003": "تنطبق هذه القواعد على جميع المساحات الإجتماعية التي نستخدمها، بما في ذلك (ولكن لا تقتصر على) Trello، GitHub، Transifex و Wikia (أي الwiki). في بعض الأحيان، سوف تنشأ حالات غير متوقعة، مثل مصدراً جديداً من الصراع أو ظهور مستحضراً للأرواح الشرية. عندما يحدث ذلك، يجوز للمشرفين تعديل هذه المبادئ التوجيهية للحفاظ على سلامة المجتمع من التهديدات الجديدة. ولكن لا تخف: سيتم إعلامك عن طريق إعلان من الآنسة Bailey إذا تغيرت المبادئ التوجيهية.",
|
||||
"commGuideHeadingInteractions": "التفاعل في Habitica",
|
||||
"commGuidePara015": "Habitica has two kinds of social spaces: public, and private. Public spaces include the Tavern, Public Guilds, GitHub, Trello, and the Wiki. Private spaces are Private Guilds, Party chat, and Private Messages. All Display Names must comply with the public space guidelines. To change your Display Name, go on the website to User > Profile and click on the \"Edit\" button.",
|
||||
"commGuidePara015": "يحتوي Habitica على نوعين من الفضاءات الاجتماعية: العامة والخاصة. تشمل الفضاءات العامة التي يمكن الوصول إليها الحانوت والمنتديات العامة و GitHub و Trello والويكي. الفضاءات الخاصة هي الأندية الخاصة والدردشة الجماعية والرسائل الخاصة. يجب على جميع أسماء العرض وأسماء المستخدمين @ اتباع إرشادات الفضاء العام. لتغيير اسم العرض و / أو اسم المستخدم @ ، انتقل إلى القائمة> الإعدادات> الملف الشخصي على الهاتف المحمول. على الويب ، انتقل إلى المستخدم> الإعدادات.",
|
||||
"commGuidePara016": "عند التنقل ما بين الأماكن العامة في Habitica، هناك بعض القواعد العامة للحفاظ على سعادة وسلامة الجميع. ستكون هذه القواعد سهلة عليك أيها المغامر!",
|
||||
"commGuideList02A": "<strong>Respect each other</strong>. Be courteous, kind, friendly, and helpful. Remember: Habiticans come from all backgrounds and have had wildly divergent experiences. This is part of what makes Habitica so cool! Building a community means respecting and celebrating our differences as well as our similarities. Here are some easy ways to respect each other:",
|
||||
"commGuideList02B": "<strong>التزم بجميع <a href='/static/terms' target='_blank'>الشروط والأحكام</a></strong>.",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"frequentlyAskedQuestions": "الأسئلة الأكثر تكراراً",
|
||||
"faqQuestion0": "أنا محتار. من أين استطيع أن احصل على نظرة عامة؟",
|
||||
"iosFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > Customize Avatar.\n\n Some basic ways to interact: click the (+) in the upper-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-left-hand corner, and expand and contract checklists by clicking on the checklist bubble.",
|
||||
"iosFaqAnswer0": "أولاً ، ستقوم بإعداد المهام التي تريد القيام بها في حياتك اليومية. ثم ، بمجرد الانتهاء من المهام في الحياة الحقيقية وتحققها ، ستكسب الخبرة والذهب. يتم استخدام الذهب لشراء المعدات وبعض العناصر ، بالإضافة إلى المكافآت المخصصة. يؤدي الخبرة إلى صعود شخصيتك وفتح محتوى مثل الحيوانات الأليفة والمهارات والمهام! يمكنك تخصيص شخصيتك في القائمة> تخصيص الصورة الرمزية.\n\n بعض الطرق الأساسية للتفاعل: انقر على (+) في الزاوية العلوية اليمنى لإضافة مهمة جديدة. انقر فوق المهمة الموجودة لتحريرها ، واسحب إلى اليسار على المهمة لحذفها. يمكنك فرز المهام باستخدام العلامات في الزاوية العلوية اليسرى ، وتوسيع وطي قوائم الاختيار عن طريق النقر على فقاعة قائمة الاختيارات.",
|
||||
"androidFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > [Inventory >] Avatar.\n\n Some basic ways to interact: click the (+) in the lower-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-right-hand corner, and expand and contract checklists by clicking on the checklist count box.",
|
||||
"webFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn Experience and Gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as pets, skills, and quests! For more detail, check out a step-by-step overview of the game at [Help -> Overview for New Users](https://habitica.com/static/overview).",
|
||||
"faqQuestion1": "كيف أضيف مهماتي؟",
|
||||
"iosFaqAnswer1": "Good Habits (the ones with a +) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a -) are tasks that you should avoid, like biting nails. Habits with a + and a - have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award experience and gold. Bad Habits subtract health.\n\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by tapping to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n\n To-Dos are your To-Do list. Completing a To-Do earns you gold and experience. You never lose health from To-Dos. You can add a due date to a To-Do by tapping to edit.",
|
||||
"iosFaqAnswer1": "العادات الجيدة (تلك التي تحتوي على علامة \"+\") هي المهام التي يمكنك القيام بها عدة مرات في اليوم، مثل تناول الخضروات. العادات السيئة (تلك التي تحتوي على علامة \"-\") هي المهام التي يجب عليك تجنبها، مثل عض الأظافر. والعادات التي تحتوي على علامة \"+\" و \"-\" لديها خيار جيد وخيار سيئ، مثل استخدام الدرج بدلاً من المصعد.\n\nالعادات الجيدة تمنحك خبرة وذهب. والعادات السيئة تقلل من صحتك.\n\nالمهام اليومية هي المهام التي يجب عليك القيام بها كل يوم، مثل تنظيف أسنانك أو التحقق من بريدك الإلكتروني. يمكنك تعديل تواريخ المهام اليومية بالنقر لتحريرها. إذا تخطيت مهمة يومية محددة، فسيتلقى أفاتارك ضررًا خلال الليل. كن حذرًا من عدم إضافة العديد من المهام اليومية في وقت واحد!\n\nالمهام التي يجب القيام بها هي قائمة المهام الخاصة بك. إكمال المهام يمنحك الذهب والخبرة. لن تفقد أبدًا صحتك بسبب هذه المهام. يمكنك إضافة تاريخ استحقاق للمهام التي يجب القيام بها بالنقر لتحريرها.",
|
||||
"androidFaqAnswer1": "Good Habits (the ones with a +) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a -) are tasks that you should avoid, like biting nails. Habits with a + and a - have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award experience and gold. Bad Habits subtract health.\n\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by tapping to edit it. If you skip a Daily that is due, your character will take damage overnight. Be careful not to add too many Dailies at once!\n\n To-Dos are your To-Do list. Completing a To-Do earns you gold and experience. You never lose health from To-Dos. You can add a due date to a To-Do by tapping to edit.",
|
||||
"webFaqAnswer1": "* Good Habits (the ones with a :heavy_plus_sign:) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a :heavy_minus_sign:) are tasks that you should avoid, like biting nails. Habits with a :heavy_plus_sign: and a :heavy_minus_sign: have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award Experience and Gold. Bad Habits subtract Health.\n* Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by clicking the pencil item to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n* To-Dos are your To-Do list. Completing a To-Do earns you Gold and Experience. You never lose Health from To-Dos. You can add a due date to a To-Do by clicking the pencil icon to edit.",
|
||||
"faqQuestion2": "ما هي أمثلة المهمات؟",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"quests": "التناقيب",
|
||||
"quest": "تنقيب",
|
||||
"petQuests": "Pet and Mount Quests",
|
||||
"unlockableQuests": "Unlockable Quests",
|
||||
"goldQuests": "Masterclasser Quest Lines",
|
||||
"quests": "مغامرات",
|
||||
"quest": "مغامرة",
|
||||
"petQuests": "مغامرات للحصول على حيوانات الأليفة ومطايا",
|
||||
"unlockableQuests": "مغامرات غير قابلة للفتح",
|
||||
"goldQuests": "مغامرات ماستركلاسر",
|
||||
"questDetails": "تفاصيل المغامرة",
|
||||
"questDetailsTitle": "Quest Details",
|
||||
"questDetailsTitle": "تفاصيل المغامرة",
|
||||
"questDescription": "Quests allow players to focus on long-term, in-game goals with the members of their party.",
|
||||
"invitations": "الدعوات",
|
||||
"completed": "\t\nمنتهى!",
|
||||
"completed": "اكتملت المغامرة!",
|
||||
"rewardsAllParticipants": "Rewards for all Quest Participants",
|
||||
"rewardsQuestOwner": "Additional Rewards for Quest Owner",
|
||||
"inviteParty": "Invite Party to Quest",
|
||||
@@ -71,5 +71,6 @@
|
||||
"bossHealth": "<%= currentHealth %> / <%= maxHealth %> Health",
|
||||
"rageAttack": "Rage Attack:",
|
||||
"bossRage": "<%= currentRage %> / <%= maxRage %> Rage",
|
||||
"rageStrikes": "Rage Strikes"
|
||||
"rageStrikes": "Rage Strikes",
|
||||
"hatchingPotionQuests": "مغامرات للحصول على جرعات سحرية"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"questEvilSantaText": "سانتا الموقع بالفخ",
|
||||
"questEvilSantaNotes": "You hear agonized roars deep in the icefields. You follow the growls - punctuated by the sound of cackling - to a clearing in the woods, where you see a fully-grown polar bear. She's caged and shackled, fighting for her life. Dancing atop the cage is a malicious little imp wearing a castaway costume. Vanquish Trapper Santa, and save the beast!",
|
||||
"questEvilSantaNotes": "تسمع زئيرًا متألمة في عمق حقول الجليد. تتبع النموات - المفصولة بصوت الضحك - إلى مساحة صافية في الغابة ، حيث ترى دبًا قطبيًا ناضجًا تمامًا. إنها مسجونة ومقيدة ، تحارب من أجل حياتها. يرقص فوق القفص عفريت صغيرة شريرة ترتدي زي الناجي. اهزم صياد سانتا وانقذ الوحش! <br> <br> <strong> ملاحظة </strong>: \"صياد سانتا\" يمنح إنجازًا قابلًا للتكديس للمهمة ولكنه يعطي جبلًا نادرًا يمكن إضافته إلى طاولة الخيل الخاصة بك مرة واحدة.",
|
||||
"questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured.",
|
||||
"questEvilSantaBoss": "سانتا الموقع بالفخ",
|
||||
"questEvilSantaDropBearCubPolarMount": "دب قطبي (مركب)",
|
||||
"questEvilSanta2Text": "البحث عن الشبل",
|
||||
"questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
|
||||
"questEvilSanta2Text": "ابحث عن الشبل",
|
||||
"questEvilSanta2Notes": "عندما اعتقل صياد السانتا الدب القطبي، هرب صغيرها إلى حقول الجليد. تسمع صوت تكسر أغصان الشجر وصوت الثلج يتكسر بينما تجوب الغابة. آثار بصمات الكفوف! تبدأ بالجري لمتابعة الأثر. ابحث عن جميع الأثر والأغصان المكسورة واسترد الصغير!<br><br><strong>ملاحظة</strong>: تمنح \"البحث عن الصغير\" إنجازًا لمهمة يمكن تراكمه ولكن يمنح حيوانًا أليفًا نادرًا يمكن إضافته إلى مستودعك مرة واحدة فقط.",
|
||||
"questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
|
||||
"questEvilSanta2CollectTracks": "مسارات",
|
||||
"questEvilSanta2CollectBranches": "أغصان مكسورة",
|
||||
|
||||
@@ -136,5 +136,8 @@
|
||||
"cancelSubInfoGoogle": "الرجاء الانتقال إلى \"الحساب\"> قسم \"الاشتراكات\" في متجر Google Play لإلغاء اشتراكك أو لمعرفة تاريخ انتهاء إشتراكك إذا كنت قد ألغيته بالفعل. هذه الشاشة غير قادرة على إظهار ما إذا كان قد تم إلغاء اشتراكك.",
|
||||
"organization": "منظمة",
|
||||
"giftASubscription": "إهداء إشتراك",
|
||||
"viewSubscriptions": "عرض الإشتراكات"
|
||||
"viewSubscriptions": "عرض الإشتراكات",
|
||||
"howManyGemsSend": "كم عدد الجواهر التي ترغب في إرسالها؟",
|
||||
"howManyGemsPurchase": "كم عدد الأحجار الكريمة التي ترغب في شرائها؟",
|
||||
"needToPurchaseGems": "هل تحتاج إلى شراء جواهر كهدية؟"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"achievement": "Achievement",
|
||||
"onwards": "Onwards!",
|
||||
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
|
||||
"reachedLevel": "You Reached Level <%= level %>",
|
||||
"achievementLostMasterclasser": "Quest Completionist: Masterclasser Series",
|
||||
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!"
|
||||
"achievement": "Дасягненьне",
|
||||
"onwards": "Наперад!",
|
||||
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
|
||||
"reachedLevel": "You Reached Level <%= level %>",
|
||||
"achievementLostMasterclasser": "Quest Completionist: Masterclasser Series",
|
||||
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!"
|
||||
}
|
||||
|
||||
@@ -93,5 +93,18 @@
|
||||
"achievementFreshwaterFriendsText": "Завършили сте мисиите за домашни любимци за аксолотъла, жабата и хипопотама.",
|
||||
"achievementFreshwaterFriends": "Сладководни приятели",
|
||||
"yourRewards": "Вашите Награди",
|
||||
"achievementBoneCollectorModalText": "Събрали сте всичките Скелетни Любимци!"
|
||||
"achievementBoneCollectorModalText": "Събрали сте всичките Скелетни Любимци!",
|
||||
"achievementRedLetterDay": "Червен Празник",
|
||||
"achievementLegendaryBestiary": "Легендарен Бестиарий",
|
||||
"achievementLegendaryBestiaryText": "Излюпили са всички стандартни цветови разновидности на Митичните животни: Дракон, Летящо прасе, Грифон, Водна змия и Еднорог!",
|
||||
"achievementLegendaryBestiaryModalText": "Събрали сте всички Митичн любимци!",
|
||||
"achievementSkeletonCrewModalText": "Събрали сте всички Скелетни животни!",
|
||||
"achievementSeeingRed": "Виждам червено",
|
||||
"achievementSkeletonCrew": "Скелетен екипаж",
|
||||
"achievementSeeingRedText": "Събрали са всичк Червени любимци.",
|
||||
"achievementRedLetterDayModalText": "Събрали сте всички Червени животни!",
|
||||
"achievementSeasonalSpecialist": "Сезонен експерт",
|
||||
"achievementRedLetterDayText": "Са събрали всички Червени животни.",
|
||||
"achievementSeeingRedModalText": "Събрали сте всички Червени любимци!",
|
||||
"achievementSkeletonCrewText": "Събрали са всички Скелетни животни."
|
||||
}
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
{}
|
||||
{
|
||||
"achievement": "Kadaugan",
|
||||
"onwards": "Padayon!",
|
||||
"levelup": "Sa pagbuhat sa imong tumong sa kinabuhi, mitaas ang imong nibel ug nahanaw ang imong sakit nga gibati.",
|
||||
"reachedLevel": "Nakamtan mo ang Nibel <%= level %>",
|
||||
"yourRewards": "Imong Daog"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,14 @@
|
||||
"achievementLegendaryBestiaryModalText": "Posbíral/a jsi všechny mytické mazlíčky!",
|
||||
"achievementLegendaryBestiaryText": "Posbíral/a všechny mytické mazlíčky: draka, létající prase, gryfona, mořského hada a jednorožce!",
|
||||
"achievementLegendaryBestiary": "Legendární bestiář",
|
||||
"achievementSeasonalSpecialist": "Sezónní specialista"
|
||||
"achievementSeasonalSpecialist": "Sezónní specialista",
|
||||
"achievementVioletsAreBlueText": "Posbíral/a všechny mazlíčky z Modré Cukrové Vaty.",
|
||||
"achievementVioletsAreBlue": "Fialky jsou Modré",
|
||||
"achievementVioletsAreBlueModalText": "Posbíral/a jsi všechny mazlíčky z Modré Cukrové Vaty.",
|
||||
"achievementSeasonalSpecialistModalText": "Dokončl/a jsi všechny sezónní úkoly!",
|
||||
"achievementDomesticatedModalText": "Sesbíral/a jsi všechna domácí zvířata!",
|
||||
"achievementSeasonalSpecialistText": "Dokončil/a jsi všechny Jarní a Zimní sezónní úkoly: Honba za vajíčky, Pastičkář Santa, a najdi Cuba!",
|
||||
"achievementWildBlueYonderText": "Ochočil/a všechny zvířata z Modré Cukrové Vaty.",
|
||||
"achievementWildBlueYonderModalText": "Ochočil/a jsi všechny mazlíčky z Modré Cukrové Vaty!",
|
||||
"achievementDomesticatedText": "Vylíhl/a všechna standardní zbarvení domácích zvířat: Fretka, morče, kohout, létající prasátko, krysa, králík, kůň a kráva!"
|
||||
}
|
||||
|
||||
@@ -340,5 +340,23 @@
|
||||
"singleCompletion": "Single - Completes when any assigned user finishes",
|
||||
"allAssignedCompletion": "All - Completes when all assigned users finish",
|
||||
"pmReported": "Tak, fordi du rapporterede denne besked.",
|
||||
"features": "Funktioner"
|
||||
"features": "Funktioner",
|
||||
"invitedToPartyBy": "<a href=\"/profile/<%- userId %>\" target=\"_blank\">@<%- userName %></a> har inviteret dig til holdet <span class=\"notification-bold\"><%- party %></span>",
|
||||
"PMUserDoesNotReceiveMessages": "Denne bruger modtager ikke længere private beskeder",
|
||||
"blockedToSendToThisUser": "Du kan ikke sende til denne spiller da du har blokeret denne spiller.",
|
||||
"blockYourself": "Du kan ikke blokere dig selv",
|
||||
"selectGift": "Vælg Gave",
|
||||
"sendGiftToWhom": "Hvem vil du gerne sende en gave til?",
|
||||
"PMDisabled": "Slå Private Beskeder fra",
|
||||
"editGuild": "Rediger Klan",
|
||||
"sendGiftTotal": "I alt:",
|
||||
"usernameOrUserId": "Indtast @brugernavn eller Bruger ID",
|
||||
"sendGiftLabel": "Vil du gerne sende en gavebesked?",
|
||||
"giftMessageTooLong": "Maximum længe for gavebeskeden er <%= maxGiftMessageLength %>.",
|
||||
"userWithUsernameOrUserIdNotFound": "Brugernavn eller Bruger ID findes ikke.",
|
||||
"editParty": "Rediger Hold",
|
||||
"leaveGuild": "Forlad Klan",
|
||||
"joinGuild": "Tilslut Klan",
|
||||
"PMUnblockUserToSendMessages": "Fjern denne brugers blokering for at sende og modtage beskeder.",
|
||||
"selectSubscription": "Vælg Abonnement"
|
||||
}
|
||||
|
||||
@@ -141,5 +141,8 @@
|
||||
"achievementWoodlandWizardText": "Du hast alle Standard-Farben der Waldkreaturen ausgebrütet: Dachs, Bär, Hirsch, Fuchs, Frosch, Igel, Eule, Schnecke, Eichhörnchen und Bäumling!",
|
||||
"achievementBoneToPickModalText": "Du hast alle klassischen und Quest-Skeletthaustiere gesammelt!",
|
||||
"achievementBoneToPick": "Ein harter Knochen",
|
||||
"achievementBoneToPickText": "Hat alle klassischen und Quest-Skeletthaustiere ausgebrütet!"
|
||||
"achievementBoneToPickText": "Hat alle klassischen und Quest-Skeletthaustiere ausgebrütet!",
|
||||
"achievementPolarProText": "Hat alle Standardfarben der Polar-Haustiere ausgebrütet: Bär, Fuchs, Pinguin, Wal und Wolf!",
|
||||
"achievementPolarPro": "Polar-Profi",
|
||||
"achievementPolarProModalText": "Du hast alle Polar-Haustiere gesammelt!"
|
||||
}
|
||||
|
||||
@@ -745,5 +745,16 @@
|
||||
"backgroundAutumnBridgeNotes": "Bewundere die Schönheit einer Brücke im Herbst.",
|
||||
"backgrounds122022": "Set 103: Veröffentlicht im Dezember 2022",
|
||||
"backgroundBranchesOfAHolidayTreeText": "Äste eines Festtagsbaums",
|
||||
"backgroundBranchesOfAHolidayTreeNotes": "Baumle auf den Ästen eines Festtagsbaums."
|
||||
"backgroundBranchesOfAHolidayTreeNotes": "Baumle auf den Ästen eines Festtagsbaums.",
|
||||
"backgrounds032023": "SET 106: Veröffentlicht im März 2023",
|
||||
"backgrounds022023": "SET 195: Veröffentlicht im Februar 2023",
|
||||
"backgroundGoldenBirdcageText": "Goldener Vogelkäfig",
|
||||
"backgroundGoldenBirdcageNotes": "Verstecken in einem goldenen Vogelkäfig.",
|
||||
"backgrounds012023": "SET 104: Veröffentlicht Januar 2023",
|
||||
"backgroundRimeIceText": "Raureif",
|
||||
"backgroundRimeIceNotes": "Bewundere funkelnden Raureif.",
|
||||
"backgroundSnowyTempleText": "Verschneiter Tempel",
|
||||
"backgroundSnowyTempleNotes": "Einen ruhigen verschneiten Tempel anschauen.",
|
||||
"backgroundSnowyVillageText": "Verschneites Dorf",
|
||||
"backgroundSnowyVillageNotes": "Ein verschneites Dorf bewundern."
|
||||
}
|
||||
|
||||
@@ -129,5 +129,6 @@
|
||||
"commGuidePara017": "Hier ist die Kurzfassung, aber wir möchten Dich ermutigen, weiter unten mehr Details zu erfahren:",
|
||||
"commGuideList02M": "Frage nicht nach oder bettle nicht um Edelsteine, Abonnements oder die Mitgliedschaft in Gruppenplänen. Nachrichten dieser Art sind weder in der Taverne, noch in öffentlichen oder privaten Chaträumen, und auch nicht in Privatnachrichten erlaubt. Wenn Du Nachrichten erhältst, in denen man Dich um bezahlte Artikel fragt, melde sie bitte über das Fähnchen. Wiederholte oder extreme Betteleien nach Edelsteinen oder Abonnements, vor allem nachdem bereits eine Warnung ausgesprochen wurde, können zu einer Kontosperre führen.",
|
||||
"commGuideList09D": "Entfernung oder Herabstufung des Mitwirkenden-Ranges",
|
||||
"commGuideList05H": "Schwerwiegende oder wiederholte Versuche, andere Spielende zu betrügen oder zu bedrängen, um an Gegenstände zu kommen, die echtes Geld kosten"
|
||||
"commGuideList05H": "Schwerwiegende oder wiederholte Versuche, andere Spielende zu betrügen oder zu bedrängen, um an Gegenstände zu kommen, die echtes Geld kosten",
|
||||
"commGuideList02N": "<strong>Markiere und melde Nachrichten, in denen diese Richtlinien oder die Nutzungsbedingungen nicht eingehalten werden.</strong> Wir werden uns so schnell wie möglich darum kümmern. Alternativ kannst du Mitarbeiter:innen über <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> benachrichtigen, doch die Markierung ist der schnellste Weg, um Hilfe zu erhalten."
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
"assignedToUser": "Zugewiesen: <strong>@<%- userName %></strong>",
|
||||
"assignedToMembers": "<%= userCount %> Mitgliedern",
|
||||
"assignedToYouAndMembers": "<strong>Dir</strong>, <%= userCount %> Mitgliedern",
|
||||
"youAreAssigned": "Dir zugewiesen",
|
||||
"youAreAssigned": "Zugewiesen: <strong>Dir<strong>",
|
||||
"taskIsUnassigned": "Diese Aufgabe ist niemandem zugewiesen",
|
||||
"confirmUnClaim": "Bist Du sicher, dass Du diese Aufgabe abgeben möchtest?",
|
||||
"confirmNeedsWork": "Bist Du sicher, dass Du diese Aufgabe auf \"Benötigt Arbeit\" setzen möchtest?",
|
||||
|
||||
@@ -235,5 +235,12 @@
|
||||
"fall2022HarpyMageSet": "Harpyie (Magier)",
|
||||
"fall2022WatcherHealerSet": "Beobachter (Heiler)",
|
||||
"gemSaleHow": "Kauf einfach zwischen <%= eventStartMonth %> <%= eventStartOrdinal %>und <%= eventEndOrdinal %> eines der Edelstein-Pakete wie normal und Deinem Konto werden automatisch die zusätzlichen Edelsteine gutgeschrieben. Das heißt insgesamt mehr Edelsteine zum ausgeben, teilen oder ansparen für zukünftige Veröffentlichungen!",
|
||||
"gemSaleLimitations": "Dieses Sonderangebot gilt nur während der zeitlich beschränkten Aktion. Die Aktion startet am <%= eventStartOrdinal %>. <%= eventStartMonth %> um 8:00 EDT (12:00 UTC) und endet am <%= eventEndOrdinal %>. <%= eventStartMonth %> um 20:00 PM EDT (00:00 UTC). Das Sonderangebot ist nur verfügbar, wenn Du Edelsteine für Dich selbst kaufst."
|
||||
"gemSaleLimitations": "Dieses Sonderangebot gilt nur während der zeitlich beschränkten Aktion. Die Aktion startet am <%= eventStartOrdinal %>. <%= eventStartMonth %> um 8:00 EDT (12:00 UTC) und endet am <%= eventEndOrdinal %>. <%= eventStartMonth %> um 20:00 PM EDT (00:00 UTC). Das Sonderangebot ist nur verfügbar, wenn Du Edelsteine für Dich selbst kaufst.",
|
||||
"winter2023WalrusWarriorSet": "Walross (Krieger)",
|
||||
"winter2023FairyLightsMageSet": "Feenlichter (Magier)",
|
||||
"winter2023CardinalHealerSet": "Kardinal (Heiler)",
|
||||
"dateStartFebruary": "8. Februar",
|
||||
"anniversaryLimitedDates": "30. Januar bis 8. Februar",
|
||||
"limitedEvent": "Limitiertes Event",
|
||||
"winter2023RibbonRogueSet": "Schleife (Schurke)"
|
||||
}
|
||||
|
||||
@@ -113,5 +113,6 @@
|
||||
"gryphatrice": "Greifatrice",
|
||||
"tooMuchFood": "Du versuchst zu viel Futter an Dein Haustier zu verfüttern, Aktion abgebrochen",
|
||||
"notEnoughFood": "Du hast nicht genug Futter",
|
||||
"invalidAmount": "Ungültige Menge Futter, positiver Integer benötigt"
|
||||
"invalidAmount": "Ungültige Menge Futter, positiver Integer benötigt",
|
||||
"jubilantGryphatrice": "Jubelnder Greifatrice"
|
||||
}
|
||||
|
||||
@@ -144,5 +144,8 @@
|
||||
"achievementBoneToPickModalText": "You collected all the Classic and Quest Skeleton Pets!",
|
||||
"achievementPolarPro": "Polar Pro",
|
||||
"achievementPolarProText": "Has hatched all standard colors of Polar pets: Bear, Fox, Penguin, Whale, and Wolf!",
|
||||
"achievementPolarProModalText": "You collected all the Polar Pets!"
|
||||
"achievementPolarProModalText": "You collected all the Polar Pets!",
|
||||
"achievementPlantParent": "Plant Parent",
|
||||
"achievementPlantParentText": "Has hatched all standard colors of Plant pets: Cactus and Treeling!",
|
||||
"achievementPlantParentModalText": "You collected all the Plant Pets!"
|
||||
}
|
||||
|
||||
@@ -845,11 +845,35 @@
|
||||
|
||||
"backgrounds012023": "SET 104: Released January 2023",
|
||||
"backgroundRimeIceText": "Rime Ice",
|
||||
"backgroundRimeIceNotes": "Admire Sparkly Rime Ice.",
|
||||
"backgroundRimeIceNotes": "Admire sparkly Rime Ice.",
|
||||
"backgroundSnowyTempleText": "Snowy Temple",
|
||||
"backgroundSnowyTempleNotes": "View a Serene Snowy Temple.",
|
||||
"backgroundSnowyTempleNotes": "View a serene Snowy Temple.",
|
||||
"backgroundWinterLakeWithSwansText": "Winter Lake With Swans",
|
||||
"backgroundWinterLakeWithSwansNotes": "Enjoy Nature at a Winter Lake With Swans.",
|
||||
"backgroundWinterLakeWithSwansNotes": "Enjoy nature at a Winter Lake With Swans.",
|
||||
|
||||
"backgrounds022023": "SET 105: Released February 2023",
|
||||
"backgroundInFrontOfFountainText": "In Front of a Fountain",
|
||||
"backgroundInFrontOfFountainNotes": "Stroll In Front of a Fountain.",
|
||||
"backgroundGoldenBirdcageText": "Golden Birdcage",
|
||||
"backgroundGoldenBirdcageNotes": "Hide out in a Golden Birdcage.",
|
||||
"backgroundFancyBedroomText": "Fancy Bedroom",
|
||||
"backgroundFancyBedroomNotes": "Luxuriate in a Fancy Bedroom.",
|
||||
|
||||
"backgrounds032023": "SET 106: Released March 2023",
|
||||
"backgroundOldTimeyBasketballCourtText": "Old Timey Basketball Court",
|
||||
"backgroundOldTimeyBasketballCourtNotes": "Shoot hoops on an Old Timey BasketBall Court.",
|
||||
"backgroundJungleWateringHoleText": "Jungle Watering Hole",
|
||||
"backgroundJungleWateringHoleNotes": "Stop for a sip at a Jungle Watering Hole.",
|
||||
"backgroundMangroveForestText": "Mangrove Forest",
|
||||
"backgroundMangroveForestNotes": "Explore the edge of the Mangrove Forest.",
|
||||
|
||||
"backgrounds042023": "SET 107: Released April 2023",
|
||||
"backgroundLeafyTreeTunnelText": "Leafy Tree Tunnel",
|
||||
"backgroundLeafyTreeTunnelNotes": "Wander through a Leafy Tree Tunnel.",
|
||||
"backgroundSpringtimeShowerText": "Springtime Shower",
|
||||
"backgroundSpringtimeShowerNotes": "See a Flowery Springtime Shower.",
|
||||
"backgroundUnderWisteriaText": "Under Wisteria",
|
||||
"backgroundUnderWisteriaNotes": "Relax Under Wisteria.",
|
||||
|
||||
"timeTravelBackgrounds": "Steampunk Backgrounds",
|
||||
"backgroundAirshipText": "Airship",
|
||||
|
||||
@@ -5,53 +5,55 @@
|
||||
"commGuideHeadingWelcome": "Welcome to Habitica!",
|
||||
"commGuidePara001": "Greetings, adventurer! Welcome to Habitica, the land of productivity, healthy living, and the occasional rampaging gryphon. We have a cheerful community full of helpful people supporting each other on their way to self-improvement. To fit in, all it takes is a positive attitude, a respectful manner, and the understanding that everyone has different skills and limitations -- including you! Habiticans are patient with one another and try to help whenever they can.",
|
||||
"commGuidePara002": "To help keep everyone safe, happy, and productive in the community, we do have some guidelines. We have carefully crafted them to make them as friendly and easy-to-read as possible. Please take the time to read them before you start chatting.",
|
||||
"commGuidePara003": "These rules apply to all of the social spaces we use, including (but not necessarily limited to) Trello, GitHub, Weblate, and the Habitica Wiki on Fandom. As communities grow and change, their rules may adapt from time to time. When there are substantive changes to these Guidelines, you'll hear about it in a Bailey announcement and/or our social media!",
|
||||
"commGuidePara003": "These rules apply to all of the social spaces we use, including (but not necessarily limited to) Trello, GitHub, Weblate, and the Habitica Wiki on Fandom. As communities grow and change, their rules may adapt from time to time. When there are substantive changes to the community rules listed here, you'll hear about it in a Bailey announcement and/or our social media!",
|
||||
"commGuideHeadingInteractions": "Interactions in Habitica",
|
||||
"commGuidePara015": "Habitica has two kinds of social spaces: public, and private. Public spaces include the Tavern, Public Guilds, GitHub, Trello, and the Wiki. Private spaces are Private Guilds, Party chat, and Private Messages. All Display Names and @usernames must comply with the public space guidelines. To change your Display Name and/or @username, on mobile go to Menu > Settings > Profile. On web, go to User > Settings.",
|
||||
"commGuidePara016": "When navigating the public spaces in Habitica, there are some general rules to keep everyone safe and happy.",
|
||||
|
||||
"commGuidePara017": "Here's the quick version, but we encourage you to read in more detail below:",
|
||||
"commGuideList01F": "Please flag posts that break our Community Guidelines or TOS.",
|
||||
"commGuideList01A": "Terms & Conditions apply across all spaces, including private guilds, party chat, and messages.",
|
||||
"commGuideList01B": "Prohibited: Any communication that is violent, threatening, promoting of discrimination etc. including memes, images, and jokes.",
|
||||
"commGuideList01C": "All discussions must be appropriate for all ages and be free of profanity.",
|
||||
"commGuideList01D": "Please comply with mod requests.",
|
||||
"commGuideList01E": "Do not instigate or engage in contentious conversation in the Tavern.",
|
||||
"commGuideList01D": "Please comply with staff requests.",
|
||||
"commGuideList01E": "<strong>Do not instigate or engage in contentious conversation in the Tavern.</strong>",
|
||||
"commGuideList01F": "No begging for paid items, spamming, or large header text/all caps.",
|
||||
|
||||
"commGuideList02N": "<strong>Flag and report posts that break these Guidelines or the Terms of Service.</strong> We will handle them as quickly as possible. You may also notify staff via <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> but flags are the fastest way to get help.",
|
||||
"commGuideList02A": "<strong>Respect each other</strong>. Be courteous, kind, friendly, and helpful. Remember: Habiticans come from all backgrounds and have had wildly divergent experiences. This is part of what makes Habitica so cool! Building a community means respecting and celebrating our differences as well as our similarities.",
|
||||
"commGuideList02B": "<strong>Obey all of the <a href='/static/terms' target='_blank'>Terms and Conditions</a></strong> in both public and private spaces.",
|
||||
"commGuideList02G": "<strong>Comply immediately with any Mod request.</strong> This could include, but is not limited to, requesting you limit your posts in a particular space, editing your profile to remove unsuitable content, asking you to move your discussion to a more suitable space, etc. Do not argue with moderators. If you have concerns or comments about moderation, email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> to contact our community manager.",
|
||||
"commGuideList02B": "<strong>Obey all of the <a href='https://habitica.com/static/terms' target='_blank'>Terms and Conditions</a></strong> in both public and private spaces.",
|
||||
"commGuideList02G": "<strong>Comply immediately with any Staff request.</strong> This could include, but is not limited to, requesting you limit your posts in a particular space, editing your profile to remove unsuitable content, asking you to move your discussion to a more suitable space, etc. Do not argue with staff. If you have concerns or comments about staff actions, email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> to contact our community manager.",
|
||||
"commGuideList02C": "<strong>Do not post images or text that are violent, threatening, or sexually explicit/suggestive, or that promote discrimination, bigotry, racism, sexism, hatred, harassment or harm against any individual or group</strong>. Not even as a joke or meme. This includes slurs as well as statements. Not everyone has the same sense of humor, and so something that you consider a joke may be hurtful to another.",
|
||||
"commGuideList02D": "<strong>Keep discussions appropriate for all ages</strong>. This means avoiding adult topics in public spaces. We have many young Habiticans who use the site, and people come from all walks of life. We want our community to be as comfortable and inclusive as possible.",
|
||||
"commGuideList02E": "<strong>Avoid profanity. This includes milder, religious-based oaths that may be acceptable elsewhere and abbreviated or obscured profanity.</strong> We have people from all religious and cultural backgrounds, and we want to make sure that all of them feel comfortable in public spaces. <strong>If a moderator or staff member tells you that a term is disallowed on Habitica, even if it is a term that you did not realize was problematic, that decision is final.</strong> Additionally, slurs will be dealt with very severely, as they are also a violation of the Terms of Service.",
|
||||
"commGuideList02F": "Avoid extended discussions of divisive topics in the Tavern and where it would be off-topic. If someone mentions something that is allowed by the guidelines but which is hurtful to you, it's okay to politely let them know that. If someone tells you you've made them uncomfortable, take time to reflect instead of responding in anger. But if you feel that a conversation is getting heated, overly emotional, or hurtful, <strong>cease to engage. Instead, report the posts to let us know about it.</strong> Moderators will respond as quickly as possible. You may also email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and include screenshots if they may be helpful.",
|
||||
"commGuideList02E": "<strong>Avoid profanity. This includes abbreviated or obscured profanity.</strong> We have people from all religious and cultural backgrounds, and we want to make sure that all of them feel comfortable in public spaces. <strong>If a staff member tells you that a term is disallowed on Habitica, even if it is a term that you did not realize was problematic, that decision is final.</strong> Additionally, slurs will be dealt with very severely, as they are also a violation of the Terms of Service.",
|
||||
"commGuideList02F": "Avoid extended discussions of divisive topics in the Tavern and where it would be off-topic. If someone mentions something that is allowed by the guidelines but which is hurtful to you, it's okay to politely let them know that. If someone tells you you've made them uncomfortable, take time to reflect instead of responding in anger. But if you feel that a conversation is getting heated, overly emotional, or hurtful, <strong>cease to engage. Instead, report the posts to let us know about it. </strong>Staff will respond as quickly as possible. You may also email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and include screenshots if they may be helpful.",
|
||||
"commGuideList02M": "Do not ask or beg for gems, subscriptions or membership in Group Plans. This is not allowed in the Tavern, public or private chat spaces, or in PMs. If you receive messages asking for paid items, please report them by flagging. Repeated or severe gem or subscription begging, especially after a warning, may result in an account ban.",
|
||||
"commGuideList02J": "<strong>Do not spam</strong>. Spamming may include, but is not limited to: posting the same comment or query in multiple places, <strong>posting links without explanation or context</strong>, posting nonsensical messages, posting multiple promotional messages about a Guild, Party or Challenge, or posting many messages in a row. If people clicking on a link will result in any benefit to you, you need to disclose that in the text of your message or that will also be considered spam. Mods may decide what constitutes spam at their discretion.",
|
||||
"commGuideList02J": "<strong>Do not spam</strong>. Spamming may include, but is not limited to: posting the same comment or query in multiple places, <strong>posting links without explanation or context</strong>, posting nonsensical messages, posting multiple promotional messages about a Guild, Party or Challenge, or posting many messages in a row. If people clicking on a link will result in any benefit to you, you need to disclose that in the text of your message or that will also be considered spam. Staff may decide what constitutes spam at their discretion.",
|
||||
"commGuideList02K": "<strong>Avoid posting large header text in the public chat spaces, particularly the Tavern</strong>. Much like ALL CAPS, it reads as if you were yelling, and interferes with the comfortable atmosphere.",
|
||||
"commGuideList02L": "<strong>We highly discourage the exchange of personal information -- particularly information that can be used to identify you -- in public chat spaces</strong>. Identifying information can include but is not limited to: your address, your email address, and your API token/password. This is for your safety! Staff or moderators may remove such posts at their discretion. If you are asked for personal information in a private Guild, Party, or PM, we highly recommend that you politely refuse and alert the staff and moderators by either 1) flagging the message, or 2) emailing <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and including screenshots.",
|
||||
"commGuideList02L": "<strong>We highly discourage the exchange of personal information -- particularly information that can be used to identify you -- in public chat spaces</strong>. Identifying information can include but is not limited to: your address, your email address, and your API token/password. This is for your safety! Staff may remove such posts at their discretion. If you are asked for personal information in a private Guild, Party, or PM, we highly recommend that you politely refuse and alert the staff by either 1) flagging the message, or 2) emailing <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and including screenshots.",
|
||||
"commGuidePara019": "<strong>In private spaces</strong>, users have more freedom to discuss whatever topics they would like, but they still may not violate the Terms and Conditions, including posting slurs or any discriminatory, violent, or threatening content. Note that, because Challenge names appear in the winner's public profile, ALL Challenge names must obey the public space guidelines, even if they appear in a private space.",
|
||||
"commGuidePara020": "<strong>Private Messages (PMs)</strong> have some additional guidelines. If someone has blocked you, do not contact them elsewhere to ask them to unblock you. Additionally, you should not send PMs to someone asking for support (since public answers to support questions are helpful to the community). Finally, do not send anyone PMs begging for paid content of any kind.",
|
||||
"commGuidePara020A": "<strong>If you see a post or private message that you believe is in violation of the public space guidelines outlined above, or if you see a post or private message that concerns you or makes you uncomfortable, you can bring it to the attention of Moderators and Staff by clicking the flag icon to report it</strong>. A Staff member or Moderator will respond to the situation as soon as possible. Please note that intentionally reporting innocent posts is an infraction of these Guidelines (see below in \"Infractions\"). You can also contact the Mods by emailing <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> You may want to do this if there are multiple problematic posts by the same person in different Guilds, or if the situation requires some explanation. You may contact us in your native language if that is easier for you: we may have to use Google Translate, but we want you to feel comfortable about contacting us if you have a problem.",
|
||||
"commGuidePara020A": "<strong>If you see a post or private message that you believe is in violation of the public space guidelines outlined above, or if you see a post or private message that concerns you or makes you uncomfortable, you can bring it to the attention of Staff by clicking the flag icon to report it</strong>. A Staff member will respond to the situation as soon as possible. Please note that intentionally reporting innocent posts is an infraction of these Guidelines (see below in \"Infractions\"). You can also contact the Staff by emailing <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> You may want to do this if there are multiple problematic posts by the same person in different Guilds, or if the situation requires some explanation. You may contact us in your native language if that is easier for you: we may have to use Google Translate, but we want you to feel comfortable about contacting us if you have a problem.",
|
||||
"commGuidePara021": "Furthermore, some public spaces in Habitica have additional guidelines.",
|
||||
|
||||
"commGuideHeadingTavern": "The Tavern",
|
||||
"commGuidePara022": "The Tavern is the main spot for Habiticans to mingle. Daniel the Innkeeper keeps the place spic-and-span, and Lemoness will happily conjure up some lemonade while you sit and chat. Just keep in mind…",
|
||||
"commGuidePara023": "<strong>Conversation tends to revolve around casual chatting and productivity or life improvement tips</strong>. Because the Tavern chat can only hold 200 messages, <strong>it isn't a good place for prolonged conversations on topics, especially sensitive ones</strong> (ex. politics, religion, depression, whether or not goblin-hunting should be banned, etc.). These conversations should be taken to an applicable Guild. A Mod may direct you to a suitable Guild, but it is ultimately your responsibility to find and post in the appropriate place.",
|
||||
"commGuidePara023": "<strong>Conversation tends to revolve around casual chatting and productivity or life improvement tips</strong>. Because the Tavern chat can only hold 200 messages, <strong>it isn't a good place for prolonged conversations on topics, especially sensitive or contentious ones</strong> (ex. politics, religion, depression, whether or not goblin-hunting should be banned, etc.). These conversations should be taken to an applicable Guild. Staff may direct you to a suitable Guild, but it is ultimately your responsibility to find and post in the appropriate place.",
|
||||
"commGuidePara024": "<strong>Don't discuss anything addictive in the Tavern</strong>. Many people use Habitica to try to quit their bad Habits. Hearing people talk about addictive/illegal substances may make this much harder for them! Respect your fellow Tavern-goers and take this into consideration. This includes, but is not exclusive to: smoking, alcohol, pornography, gambling, and drug use/abuse.",
|
||||
|
||||
"commGuideHeadingPublicGuilds": "Public Guilds",
|
||||
"commGuidePara029": "<strong>Public Guilds are much like the Tavern, except that instead of being centered around general conversation, they have a focused theme</strong>. Public Guild chat should focus on this theme. For example, members of the Wordsmiths Guild might be cross if the conversation is suddenly focusing on gardening instead of writing, and a Dragon-Fanciers Guild might not have any interest in deciphering ancient runes. Some Guilds are more lax about this than others, but in general, <strong>try to stay on topic</strong>!",
|
||||
"commGuidePara031": "Some public Guilds will contain sensitive topics such as depression, religion, politics, etc. This is fine as long as the conversations therein do not violate any of the Terms and Conditions or Public Space Rules, and as long as they stay on topic.",
|
||||
"commGuidePara033": "<strong>Public Guilds may NOT contain 18+ content. If they plan to regularly discuss sensitive content, they should say so in the Guild description</strong>. This is to keep Habitica safe and comfortable for everyone.",
|
||||
"commGuidePara035": "<strong>If the Guild in question has different kinds of sensitive issues, it is respectful to your fellow Habiticans to place your comment behind a warning (ex. \"Warning: references self-harm\")</strong>. These may be characterized as trigger warnings and/or content notes, and Guilds may have their own rules in addition to those given here. If possible, please use <a href='https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet' target='_blank'>markdown</a> to hide the potentially sensitive content below line breaks so that those who may wish to avoid reading it can scroll past it without seeing the content. Habitica staff and moderators may still remove this material at their discretion.",
|
||||
"commGuidePara035": "<strong>If the Guild in question has different kinds of sensitive issues, it is respectful to your fellow Habiticans to include a warning (ex. \"Warning: references self-harm\")</strong>. These may be characterized as trigger warnings and/or content notes, and Guilds may have their own rules in addition to those given here. Habitica staff may still remove this material at their discretion.",
|
||||
"commGuidePara036": "Additionally, the sensitive material should be topical -- bringing up self-harm in a Guild focused on fighting depression may make sense, but is probably less appropriate in a music Guild. If you see someone who is repeatedly violating this guideline, especially after several requests, please report the posts.",
|
||||
"commGuidePara037": "<strong>No Guilds, Public or Private, should be created for the purpose of attacking any group or individual</strong>. Creating such a Guild is grounds for an instant ban. Fight bad habits, not your fellow adventurers!",
|
||||
"commGuidePara038": "<strong>All Tavern Challenges and Public Guild Challenges must comply with these rules as well</strong>.",
|
||||
|
||||
"commGuideHeadingInfractionsEtc": "Infractions, Consequences, and Restoration",
|
||||
"commGuideHeadingInfractions": "Infractions",
|
||||
"commGuidePara050": "Overwhelmingly, Habiticans assist each other, are respectful, and work to make the whole community fun and friendly. However, once in a blue moon, something that a Habitican does may violate one of the above guidelines. When this happens, the Mods will take whatever actions they deem necessary to keep Habitica safe and comfortable for everyone.",
|
||||
"commGuidePara051": "<strong>There are a variety of infractions, and they are dealt with depending on their severity</strong>. These are not comprehensive lists, and the Mods can make decisions on topics not covered here at their own discretion. The Mods will take context into account when evaluating infractions.",
|
||||
"commGuidePara050": "Overwhelmingly, Habiticans assist each other, are respectful, and work to make the whole community fun and friendly. However, once in a blue moon, something that a Habitican does may violate one of the above guidelines. When this happens, the Staff will take whatever actions they deem necessary to keep Habitica safe and comfortable for everyone.",
|
||||
"commGuidePara051": "<strong>There are a variety of infractions, and they are dealt with depending on their severity</strong>. These are not comprehensive lists, and the Staff can make decisions on topics not covered here at their own discretion. The Staff will take context into account when evaluating infractions.",
|
||||
|
||||
"commGuideHeadingSevereInfractions": "Severe Infractions",
|
||||
"commGuidePara052": "Severe infractions greatly harm the safety of Habitica's community and users, and therefore have severe consequences as a result.",
|
||||
@@ -59,16 +61,16 @@
|
||||
"commGuideList05A": "Violation of Terms and Conditions",
|
||||
"commGuideList05B": "Hate Speech/Images, Harassment/Stalking, Cyber-Bullying, Flaming, and Trolling",
|
||||
"commGuideList05C": "Violation of Probation",
|
||||
"commGuideList05D": "Impersonation of Staff or Moderators - this includes claiming user-created spaces not affiliated with Habitica are official and/or moderated by Habitica or its Mods/staff",
|
||||
"commGuideList05D": "Impersonation of Staff - this includes claiming user-created spaces not affiliated with Habitica are official and/or moderated by Habitica or its Staff",
|
||||
"commGuideList05E": "Repeated Moderate Infractions",
|
||||
"commGuideList05F": "Creation of a duplicate account to avoid consequences (for example, making a new account to chat after having chat privileges revoked)",
|
||||
"commGuideList05G": "Intentional deception of Staff or Moderators in order to avoid consequences or to get another user in trouble",
|
||||
"commGuideList05G": "Intentional deception of Staff in order to avoid consequences or to get another user in trouble",
|
||||
"commGuideList05H": "Severe or repeated attempts to defraud or pressure other players for real-money items",
|
||||
|
||||
"commGuideHeadingModerateInfractions": "Moderate Infractions",
|
||||
"commGuidePara054": "Moderate infractions do not make our community unsafe, but they do make it unpleasant. These infractions will have moderate consequences. When in conjunction with multiple infractions, the consequences may grow more severe.",
|
||||
"commGuidePara055": "The following are some examples of Moderate Infractions. This is not a comprehensive list.",
|
||||
"commGuideList06A": "Ignoring, disrespecting or arguing with a Mod. This includes publicly complaining about moderators or other users, publicly glorifying or defending banned users, or debating whether or not a moderator action was appropriate. If you are concerned about one of the rules or the behaviour of the Mods, please contact the staff via email (<a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>).",
|
||||
"commGuideList06A": "<strong>Ignoring, disrespecting or arguing with Staff.</strong> This includes publicly complaining about staff or other users, publicly glorifying or defending banned users, or debating whether or not a staff action was appropriate. If you are concerned about one of the rules or the behavior of the Staff, please contact us via email (<a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>).",
|
||||
"commGuideList06B": "Backseat Modding. To quickly clarify a relevant point: A friendly mention of the rules is fine. Backseat modding consists of telling, demanding, and/or strongly implying that someone must take an action that you describe to correct a mistake. You can alert someone to the fact that they have committed a transgression, but please do not demand an action -- for example, saying, \"Just so you know, profanity is discouraged in the Tavern, so you may want to delete that,\" would be better than saying, \"I'm going to have to ask you to delete that post.\"",
|
||||
"commGuideList06C": "Intentionally flagging innocent posts.",
|
||||
"commGuideList06D": "Repeatedly Violating Public Space Guidelines",
|
||||
@@ -78,14 +80,14 @@
|
||||
"commGuidePara056": "Minor Infractions, while discouraged, still have minor consequences. If they continue to occur, they can lead to more severe consequences over time.",
|
||||
"commGuidePara057": "The following are some examples of Minor Infractions. This is not a comprehensive list.",
|
||||
"commGuideList07A": "First-time violation of Public Space Guidelines",
|
||||
"commGuideList07B": "Any statements or actions that trigger a \"Please Don't\" from a Mod. When you are asked not to do something publicly, this in itself can be a consequence. If Mods have to issue many of these corrections to the same person, it may count as a larger infraction",
|
||||
"commGuideList07B": "Any statements or actions that trigger a \"Please Don't\" from a Staff member. When you are asked not to do something publicly, this in itself can be a consequence. If Staff have to issue many of these corrections to the same person, it may count as a larger infraction",
|
||||
|
||||
"commGuidePara057A": "Some posts may be hidden because they contain sensitive information or might give people the wrong idea. Typically this does not count as an infraction, particularly not the first time it happens!",
|
||||
|
||||
"commGuideHeadingConsequences": "Consequences",
|
||||
"commGuidePara058": "In Habitica -- as in real life -- every action has a consequence, whether it is getting fit because you've been running, getting cavities because you've been eating too much sugar, or passing a class because you've been studying.",
|
||||
"commGuidePara059": "<strong>Similarly, all infractions have direct consequences.</strong> Some sample consequences are outlined below.",
|
||||
"commGuidePara060": "<strong>If your infraction has a moderate or severe consequence, there will be a post from a staff member or moderator in the forum in which the infraction occurred explaining</strong>:",
|
||||
"commGuidePara060": "<strong>If your infraction has a moderate or severe consequence, if appropriate for the circumstances, there will be a post from a staff member in the forum in which the infraction occurred explaining</strong>:",
|
||||
"commGuideList08A": "what your infraction was",
|
||||
"commGuideList08B": "what the consequence is",
|
||||
"commGuideList08C": "what to do to correct the situation and restore your status, if possible.",
|
||||
@@ -97,35 +99,29 @@
|
||||
"commGuideList09D": "Removal or demotion of Contributor Tiers",
|
||||
"commGuideHeadingModerateConsequences": "Examples of Moderate Consequences",
|
||||
"commGuideList10A": "Restricted public and/or private chat privileges",
|
||||
"commGuideList10A1": "If your actions result in revocation of your chat privileges, a Moderator or Staff member will PM you and/or post in the forum in which you were muted to notify you of the reason for your muting and the length of time for which you will be muted and/or the action required for reinstatement. You will be reinstated if you comply politely with the actions required and agree to abide by the Community Guidelines and ToS",
|
||||
"commGuideList10C": "Restricted Guild/Challenge creation privileges",
|
||||
"commGuideList10A1": "If your actions result in revocation of your chat privileges, you must email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>. You may be reinstated at staff discretion if you comply politely with the actions required and agree to abide by the Community Guidelines and ToS",
|
||||
"commGuideList10D": "Temporarily disabling (\"freezing\") progression through Contributor Tiers",
|
||||
"commGuideList10F": "Putting users on \"Probation\"",
|
||||
"commGuideHeadingMinorConsequences": "Examples of Minor Consequences",
|
||||
"commGuideList11A": "Reminders of Public Space Guidelines",
|
||||
"commGuideList11B": "Warnings",
|
||||
"commGuideList11C": "Requests",
|
||||
"commGuideList11D": "Deletions (Mods/Staff may delete problematic content)",
|
||||
"commGuideList11E": "Edits (Mods/Staff may edit problematic content)",
|
||||
"commGuideList11D": "Deletions (Staff may delete problematic content)",
|
||||
"commGuideList11E": "Edits (Staff may edit problematic content)",
|
||||
|
||||
"commGuideHeadingRestoration": "Restoration",
|
||||
"commGuidePara061": "Habitica is a land devoted to self-improvement, and we believe in second chances. <strong>If you commit an infraction and receive a consequence, view it as a chance to evaluate your actions and strive to be a better member of the community</strong>.",
|
||||
"commGuidePara062": "The announcement, message, and/or email that you receive explaining the consequences of your actions is a good source of information. Cooperate with any restrictions which have been imposed, and endeavor to meet the requirements to have any penalties lifted.",
|
||||
"commGuidePara063": "If you do not understand your consequences, or the nature of your infraction, ask the Staff/Moderators for help so you can avoid committing infractions in the future. If you feel a particular decision was unfair, you can contact the staff to discuss it at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>.",
|
||||
"commGuidePara063": "If you do not understand your consequences, or the nature of your infraction, ask the Staff for help so you can avoid committing infractions in the future. If you feel a particular decision was unfair, you can contact the staff to discuss it at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>.",
|
||||
|
||||
"commGuideHeadingMeet": "Meet the Staff and Mods!",
|
||||
"commGuidePara006": "Habitica has some tireless knights-errant who join forces with the staff members to keep the community calm, contented, and free of trolls. Each has a specific domain, but will sometimes be called to serve in other social spheres.",
|
||||
"commGuidePara007": "Staff have purple tags marked with crowns. Their title is \"Heroic\".",
|
||||
"commGuidePara008": "Mods have dark blue tags marked with stars. Their title is \"Guardian\".",
|
||||
"commGuideHeadingMeet": "Meet the Staff",
|
||||
"commGuidePara007": "The Habitica Staff keep the app and sites running and can act as chat moderators. They have purple tags marked with crowns. Their title is \"Heroic\".",
|
||||
"commGuidePara009": "The current Staff Members are (from left to right):",
|
||||
"commGuideAKA": "<%= habitName %> aka <%= realName %>",
|
||||
"commGuideOnGitHub": "<%= gitHubName %> on GitHub",
|
||||
"commGuidePara010": "There are also several Moderators who assist the staff members. They were selected carefully, so please give them your respect and listen to their suggestions.",
|
||||
"commGuidePara011": "The current Moderators are (from left to right):",
|
||||
"commGuidePara011b": "on GitHub/Fandom",
|
||||
"commGuidePara011c": "on the Wiki",
|
||||
"commGuidePara011d": "on GitHub",
|
||||
"commGuidePara012": "If you have an issue or concern about a particular Mod, please send an email to our Staff (<a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>).",
|
||||
"commGuidePara013": "In a community as big as Habitica, users come and go, and sometimes a staff member or moderator needs to lay down their noble mantle and relax. The following are Staff and Moderators Emeritus. They no longer act with the power of a Staff member or Moderator, but we would still like to honor their work!",
|
||||
"commGuidePara014": "Staff and Moderators Emeritus:",
|
||||
|
||||
|
||||
@@ -310,6 +310,8 @@
|
||||
"hatchingPotionOnyx": "Onyx",
|
||||
"hatchingPotionVirtualPet": "Virtual Pet",
|
||||
"hatchingPotionPorcelain": "Porcelain",
|
||||
"hatchingPotionPinkMarble": "Pink Marble",
|
||||
"hatchingPotionTeaShop": "Tea Shop",
|
||||
|
||||
"hatchingPotionNotes": "Pour this on an egg, and it will hatch as a <%= potText(locale) %> pet.",
|
||||
"premiumPotionAddlNotes": "Not usable on quest pet eggs. Available for purchase until <%= date(locale) %>.",
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"pkQuestion1": "What inspired Habitica? How did it start?",
|
||||
"pkAnswer1": "If you’ve ever invested time in leveling up a character in a game, it’s hard not to wonder how great your life would be if you put all of that effort into improving your real-life self instead of your avatar. We starting building Habitica to address that question. <br /> Habitica officially launched with a Kickstarter in 2013, and the idea really took off. Since then, it’s grown into a huge project, supported by our awesome open-source volunteers and our generous users.",
|
||||
"pkQuestion2": "Why does Habitica work?",
|
||||
"pkAnswer2": "Forming a new habit is hard because people really need that obvious, instant reward. For example, it’s tough to start flossing, because even though our dentist tells us that it's healthier in the long run, in the immediate moment it just makes your gums hurt. <br /> Habitica's gamification adds a sense of instant gratification to everyday objectives by rewarding a tough task with experience, gold… and maybe even a random prize, like a dragon egg! This helps keep people motivated even when the task itself doesn't have an intrinsic reward, and we've seen people turn their lives around as a result. You can check out success stories here: https://habitversary.tumblr.com",
|
||||
"pkAnswer2": "Forming a new habit is hard because people really need that obvious, instant reward. For example, it’s tough to start flossing, because even though our dentist tells us that it's healthier in the long run, in the immediate moment it just makes your gums hurt. <br /> Habitica's gamification adds a sense of instant gratification to everyday objectives by rewarding a tough task with experience, gold… and maybe even a random prize, like a dragon egg! This helps keep people motivated even when the task itself doesn't have an intrinsic reward, and we've seen people turn their lives around as a result.",
|
||||
"pkQuestion3": "Why did you add social features?",
|
||||
"pkAnswer3": "Social pressure is a huge motivating factor for a lot of people, so we knew that we wanted to have a strong community that would hold each other accountable for their goals and cheer for their successes. Luckily, one of the things that multiplayer video games do best is foster a sense of community among their users! Habitica’s community structure borrows from these types of games; you can form a small Party of close friends, but you can also join a larger, shared-interest groups known as a Guild. Although some users choose to play solo, most decide to form a support network that encourages social accountability through features such as Quests, where Party members pool their productivity to battle monsters together.",
|
||||
"pkQuestion4": "Why does skipping tasks remove your avatar’s health?",
|
||||
@@ -179,5 +179,6 @@
|
||||
"signup": "Sign Up",
|
||||
"getStarted": "Get Started!",
|
||||
"mobileApps": "Mobile Apps",
|
||||
"learnMore": "Learn More"
|
||||
"learnMore": "Learn More",
|
||||
"translateHabitica": "Translate Habitica"
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@
|
||||
"weaponSpecialFall2022HealerNotes": "To claim victory, hold it forth and utter the words of command: 'Eye One!' Increases Intelligence by <%= int %>. Limited Edition 2022 Fall Gear.",
|
||||
|
||||
"weaponSpecialWinter2023RogueText": "Green Satin Sash",
|
||||
"weaponSpecialWinter2023RogueNotes": "Legends tell of Rogues who snare their opponents' weapons, disarm them, then gift the item back just to be cute. Incrases Strength by <%= str %>. Limited Edition 2022-2023 Winter Gear.",
|
||||
"weaponSpecialWinter2023RogueNotes": "Legends tell of Rogues who snare their opponents' weapons, disarm them, then gift the item back just to be cute. Increases Strength by <%= str %>. Limited Edition 2022-2023 Winter Gear.",
|
||||
"weaponSpecialWinter2023WarriorText": "Tusk Spear",
|
||||
"weaponSpecialWinter2023WarriorNotes": "The two prongs of this spear are shaped like walrus tusks but are twice as powerful. Jab at doubts and at silly poems until they back off! Increases Strength by <%= str %>. Limited Edition 2022-2023 Winter Gear.",
|
||||
"weaponSpecialWinter2023MageText": "Foxfire",
|
||||
@@ -468,6 +468,15 @@
|
||||
"weaponSpecialWinter2023HealerText": "Throwing Wreath",
|
||||
"weaponSpecialWinter2023HealerNotes": "Watch this festive, prickly wreath spin through the air toward your enemy or obstacles and return to you like a boomerang for another throw. Increases Intelligence by <%= int %>. Limited Edition 2022-2023 Winter Gear.",
|
||||
|
||||
"weaponSpecialSpring2023RogueText": "Nibbled Leaf",
|
||||
"weaponSpecialSpring2023RogueNotes": "Slash! Swat! Snack! Get strong and ready for your coming metamorphosis. Increases Strength by <%= str %>. Limited Edition 2023 Spring Gear.",
|
||||
"weaponSpecialSpring2023WarriorText": "Hummingbird Foil",
|
||||
"weaponSpecialSpring2023WarriorNotes": "En garde! Fend off foes from your flowers with this foil! Increases Strength by <%= str %>. Limited Edition 2023 Spring Gear.",
|
||||
"weaponSpecialSpring2023MageText": "Moonstone Magic",
|
||||
"weaponSpecialSpring2023MageNotes": "The greater their glow, the more potent is their power. Increases Intelligence by <%= int %>. Limited Edition 2023 Spring Gear.",
|
||||
"weaponSpecialSpring2023HealerText": "Lilium Pollen",
|
||||
"weaponSpecialSpring2023HealerNotes": "With a puff and a sparkle, you deploy new growth, joy, and color. Increases Intelligence by <%= int %>. Limited Edition 2023 Spring Gear.",
|
||||
|
||||
"weaponMystery201411Text": "Pitchfork of Feasting",
|
||||
"weaponMystery201411Notes": "Stab your enemies or dig in to your favorite foods - this versatile pitchfork does it all! Confers no benefit. November 2014 Subscriber Item.",
|
||||
"weaponMystery201502Text": "Shimmery Winged Staff of Love and Also Truth",
|
||||
@@ -1131,6 +1140,15 @@
|
||||
"armorSpecialWinter2023HealerText": "Cardinal Suit",
|
||||
"armorSpecialWinter2023HealerNotes": "This bright cardinal suit is perfect for flying high above your problems. Increases Constitution by <%= con %>. Limited Edition 2022-2023 Winter Gear.",
|
||||
|
||||
"armorSpecialSpring2023RogueText": "Caterpillar Cape",
|
||||
"armorSpecialSpring2023RogueNotes": "You may only have four limbs to work with, but you can climb and crawl with the greatest of grubs. Increases Perception by <%= per %>. Limited Edition 2023 Spring Gear.",
|
||||
"armorSpecialSpring2023WarriorText": "Hummingbird Armor",
|
||||
"armorSpecialSpring2023WarriorNotes": "That humming sound you hear is your wings beating faster than you can imagine. Increases Constitution by <%= con %>. Limited Edition 2023 Spring Gear.",
|
||||
"armorSpecialSpring2023MageText": "Moonstone Suit",
|
||||
"armorSpecialSpring2023MageNotes": "This snazzy spring suit magnifies moonstone magic. Increases Intelligence by <%= int %>. Limited Edition 2023 Spring Gear.",
|
||||
"armorSpecialSpring2023HealerText": "Lily Leaf Gown",
|
||||
"armorSpecialSpring2023HealerNotes": "A sweep of verdant glory to make you the envy of the Party. Increases Constitution by <%= con %>. Limited Edition 2023 Spring Gear.",
|
||||
|
||||
"armorMystery201402Text": "Messenger Robes",
|
||||
"armorMystery201402Notes": "Shimmering and strong, these robes have many pockets to carry letters. Confers no benefit. February 2014 Subscriber Item.",
|
||||
"armorMystery201403Text": "Forest Walker Armor",
|
||||
@@ -1245,6 +1263,9 @@
|
||||
"armorMystery202210Notes": "Try slithering for a change, you may find it's quite an efficient mode of transportation! Confers no benefit. October 2022 Subscriber Item.",
|
||||
"armorMystery202212Text": "Glacial Dress",
|
||||
"armorMystery202212Notes": "The universe can be cold, but this charming dress will keep you cozy as you fly. Confers no benefit. December 2022 Subscriber Item.",
|
||||
"armorMystery202304Text": "Tiptop Teapot Armor",
|
||||
"armorMystery202304Notes": "Here is your handle and here is your spout! Confers no benefit. April 2023 Subscriber Item.",
|
||||
|
||||
"armorMystery301404Text": "Steampunk Suit",
|
||||
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
|
||||
"armorMystery301703Text": "Steampunk Peacock Gown",
|
||||
@@ -1428,7 +1449,11 @@
|
||||
"armorArmoireJewelersApronNotes": "This heavy-duty apron is just the thing to wear when you feel creative. Best of all, there are dozens of small pockets to hold everything you need. Increases Intelligence by <%= int %>. Enchanted Armoire: Jeweler Set (Item 1 of 4).",
|
||||
"armorArmoireShawlCollarCoatText": "Shawl-Collar Coat",
|
||||
"armorArmoireShawlCollarCoatNotes": "A wise wizard once said there’s nothing better than being both cozy and productive! Wear this warm and stylish coat as you conquer the year’s challenges. Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
|
||||
|
||||
"armorArmoireTeaGownText": "Tea Party Gown",
|
||||
"armorArmoireTeaGownNotes": "You’re resilient, creative, brilliant, and so fashionable! Increases Strength and Intelligence by <%= attrs %> each. Enchanted Armoire: Tea Party Set (Item 1 of 3).",
|
||||
"armorArmoireBasketballUniformText": "Basketball Uniform",
|
||||
"armorArmoireBasketballUniformNotes": "Wondering what’s printed on the back of this uniform? It’s your lucky number, of course! Increases Perception by <% per %>. Enchanted Armoire: Old Timey Basketball Set (Item 1 of 2).",
|
||||
|
||||
"headgear": "helm",
|
||||
"headgearCapitalized": "Headgear",
|
||||
|
||||
@@ -1869,6 +1894,15 @@
|
||||
"headSpecialWinter2023HealerText": "Cardinal Helm",
|
||||
"headSpecialWinter2023HealerNotes": "This cardinal helm is perfect for whistling and singing to herald the winter season. Increases Intelligence by <%= int %>. Limited Edition 2022-2023 Winter Gear.",
|
||||
|
||||
"headSpecialSpring2023RogueText": "Caterpillar Cowl",
|
||||
"headSpecialSpring2023RogueNotes": "Be sure to tuck in those tempting antennae when birds are hunting overhead! Increases Perception by <%= per %>. Limited Edition 2023 Spring Gear.",
|
||||
"headSpecialSpring2023WarriorText": "Hummingbird Helmet",
|
||||
"headSpecialSpring2023WarriorNotes": "Cover your visage in iridescent feathers when you fly into battle. Increases Strength by <%= str %>. Limited Edition 2023 Spring Gear.",
|
||||
"headSpecialSpring2023MageText": "Moonstone Visor",
|
||||
"headSpecialSpring2023MageNotes": "You’ll want to wear these glasses at night so you can see clearly by the light of the moon. Increases Perception by <%= per %>. Limited Edition 2023 Spring Gear.",
|
||||
"headSpecialSpring2023HealerText": "Lily Bloom",
|
||||
"headSpecialSpring2023HealerNotes": "This brilliant and colorful display shares a color scheme with the Orb of Rebirth! How symbolic! Increases Intelligence by <%= int %>. Limited Edition 2023 Spring Gear.",
|
||||
|
||||
"headSpecialGaymerxText": "Rainbow Warrior Helm",
|
||||
"headSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special helmet is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
|
||||
|
||||
@@ -2014,6 +2048,11 @@
|
||||
"headMystery202211Notes": "Be careful with this powerful hat, its effect on admirers can be quite shocking! Confers no benefit. November 2022 Subscriber Item.",
|
||||
"headMystery202301Text": "Valiant Vulpine Ears",
|
||||
"headMystery202301Notes": "Your hearing will be so sharp you'll hear the dawn breaking and the dew sparkling. Confers no benefit. January 2023 Subscriber Item.",
|
||||
"headMystery202303Text": "Mane Character Hair",
|
||||
"headMystery202303Notes": "What better way to let everyone know you’re the star of this tale than to have blue and improbably spiky hair? Confers no benefit. March 2023 Subscriber Item.",
|
||||
"headMystery202304Text": "Tiptop Teapot Lid",
|
||||
"headMystery202304Notes": "Wear this helm for your own safe-tea. Confers no benefit. April 2023 Subscriber Item.",
|
||||
|
||||
"headMystery301404Text": "Fancy Top Hat",
|
||||
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
|
||||
"headMystery301405Text": "Basic Top Hat",
|
||||
@@ -2189,6 +2228,10 @@
|
||||
"headArmoireStrawRainHatNotes": "You’ll be able to spot every obstacle in your path when you wear this water-resistant, conical hat. Increases Perception by <%= per %>. Enchanted Armoire: Straw Raincoat Set (Item 2 of 2).",
|
||||
"headArmoireFancyPirateHatText": "Fancy Pirate Hat",
|
||||
"headArmoireFancyPirateHatNotes": "Be protected from the sun and any seagulls flying overhead as you drink tea on the deck of your ship. Increases Perception by <%= per %>. Enchanted Armoire: Fancy Pirate Set (Item 2 of 3).",
|
||||
"headArmoireTeaHatText": "Tea Party Hat",
|
||||
"headArmoireTeaHatNotes": "This elegant hat is both fancy and functional. Increases Perception by <%= per %>. Enchanted Armoire: Tea Party Set (Item 2 of 3).",
|
||||
"headArmoireBeaniePropellerHatText": "Beanie Propeller Hat",
|
||||
"headArmoireBeaniePropellerHatNotes": "This isn’t the time to keep your feet on the ground! Spin this little propeller and rise as high as your ambitions will take you. Increases all stats by <%= attrs %>. Enchanted Armoire: Independent Item.",
|
||||
|
||||
"offhand": "off-hand item",
|
||||
"offHandCapitalized": "Off-Hand Item",
|
||||
@@ -2436,6 +2479,11 @@
|
||||
"shieldSpecialWinter2023HealerText": "Cool Jams",
|
||||
"shieldSpecialWinter2023HealerNotes": "Your song of frost and snow will soothe the spirits of all who hear. Increases Constitution by <%= con %>. Limited Edition 2022-2023 Winter Gear.",
|
||||
|
||||
"shieldSpecialSpring2023WarriorText": "Flower Bunch",
|
||||
"shieldSpecialSpring2023WarriorNotes": "Collect the spring’s best flowers into this brightly colored floral bunch. Increases Constitution by <%= con %>. Limited Edition 2023 Spring Gear.",
|
||||
"shieldSpecialSpring2023HealerText": "Lily Corsage",
|
||||
"shieldSpecialSpring2023HealerNotes": "An accent for a healing visit, or part of a ritual for attending a springtime dance! Increases Constitution by <%= con %>. Limited Edition 2023 Spring Gear.",
|
||||
|
||||
"shieldMystery201601Text": "Resolution Slayer",
|
||||
"shieldMystery201601Notes": "This blade can be used to parry away all distractions. Confers no benefit. January 2016 Subscriber Item.",
|
||||
"shieldMystery201701Text": "Time-Freezer Shield",
|
||||
@@ -2595,7 +2643,10 @@
|
||||
"shieldArmoireBubblingCauldronNotes": "The perfect cauldron for brewing up a productivity potion or cooking a savory soup. In fact, there is little difference between the two! Increases Constitution by <%= con %>. Enchanted Armoire: Cooking Implements Set (Item 2 of 2).",
|
||||
"shieldArmoireJewelersPliersText": "Jeweler's Pliers",
|
||||
"shieldArmoireJewelersPliersNotes": "They cut, twist, pinch, and more. This tool can help you create whatever you can imagine. Increases Strength by <%= str %>. Enchanted Armoire: Jeweler Set (Item 3 of 4).",
|
||||
|
||||
"shieldArmoireTeaKettleText": "Tea Kettle",
|
||||
"shieldArmoireTeaKettleNotes": "All your favorite, flavorful teas can be brewed in this kettle. Are you in the mood for black tea, green tea, oolong, or perhaps an herbal infusion? Increases Constitution by <%= con %>. Enchanted Armoire: Tea Party Set (Item 3 of 3).",
|
||||
"shieldArmoireBasketballText": "Basketball",
|
||||
"shieldArmoireBasketballNotes": "Swish! Whenever you shoot this magic basketball, there will be nothing but net. Increases Constitution and Strength by <%= attrs %> each. Enchanted Armoire: Old Timey Basketball Set (Item 2 of 2).",
|
||||
|
||||
"back": "Back Accessory",
|
||||
"backBase0Text": "No Back Accessory",
|
||||
@@ -2965,6 +3016,8 @@
|
||||
"eyewearMystery202204BNotes": "What's your mood today? Express yourself with these fun screens. Confers no benefit. April 2022 Subscriber Item.",
|
||||
"eyewearMystery202208Text": "Sparkly Eyes",
|
||||
"eyewearMystery202208Notes": "Lull your enemies into a false sense of security with these terrifyingly cute peepers. Confers no benefit. August 2022 Subscriber Item.",
|
||||
"eyewearMystery202303Text": "Dreamy Eyes",
|
||||
"eyewearMystery202303Notes": "Let your nonchalant expression lure your enemies into a false sense of security. Confers no benefit. March 2023 Subscriber Item.",
|
||||
"eyewearMystery301404Text": "Eyewear Goggles",
|
||||
"eyewearMystery301404Notes": "No eyewear could be fancier than a pair of goggles - except, perhaps, for a monocle. Confers no benefit. April 3015 Subscriber Item.",
|
||||
"eyewearMystery301405Text": "Monocle",
|
||||
|
||||
@@ -212,5 +212,8 @@
|
||||
"contactForm": "Contact the Moderation Team",
|
||||
"loadEarlierMessages": "Load Earlier Messages",
|
||||
"askQuestion": "Ask a Question",
|
||||
"emptyReportBugMessage": "Report Bug Message missing"
|
||||
"emptyReportBugMessage": "Report Bug Message missing",
|
||||
"leaveHabitica": "You are about to leave Habitica.com",
|
||||
"leaveHabiticaText": "Habitica is not responsible for the content of any linked website that is not owned or operated by HabitRPG.<br>Please note that these websites' practices may differ from Habitica’s community guidelines.",
|
||||
"skipExternalLinkModal": "Hold CTRL (Windows) or Command (Mac) when clicking a link to skip this modal."
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"invite": "Invite",
|
||||
"leave": "Leave",
|
||||
"invitedToParty": "You were invited to join the Party <span class=\"notification-bold\"><%- party %></span>",
|
||||
"invitedToPartyBy": "<a href=\"/profile/<%- userId %>\" target=\"_blank\">@<%- userName %></a> has invited you to join the Party <span class=\"notification-bold\"><%- party %></span>",
|
||||
"invitedToPrivateGuild": "You were invited to join the private Guild <span class=\"notification-bold\"><%- guild %></span>",
|
||||
"invitedToPublicGuild": "You were invited to join the Guild <span class=\"notification-bold-blue\"><%- guild %></span>",
|
||||
"invitationAcceptedHeader": "Your Invitation has been Accepted",
|
||||
@@ -334,7 +335,7 @@
|
||||
"teamBasedTasksList": "Team-Based Task List",
|
||||
"teamBasedTasksListDesc": "Set up an easily-viewed shared task list for the group. Assign tasks to your fellow group members, or let them claim their own tasks to make it clear what everyone is working on!",
|
||||
"groupManagementControls": "Group Management Controls",
|
||||
"groupManagementControlsDesc": "Use task approvals to verify that a task that was really completed, add Group Managers to share responsibilities, and enjoy a private group chat for all team members.",
|
||||
"groupManagementControlsDesc": "View task status to verify that a task that was completed, add Group Managers to share responsibilities, and enjoy a private group chat for all team members.",
|
||||
"inGameBenefits": "In-Game Benefits",
|
||||
"inGameBenefitsDesc": "Group members get an exclusive Jackalope Mount, as well as full subscription benefits, including special monthly equipment sets and the ability to buy gems with gold.",
|
||||
"inspireYourParty": "Inspire your party, gamify life together.",
|
||||
|
||||
@@ -194,7 +194,11 @@
|
||||
"winter2023WalrusWarriorSet": "Walrus (Warrior)",
|
||||
"winter2023FairyLightsMageSet": "Fairy Lights (Mage)",
|
||||
"winter2023CardinalHealerSet": "Cardinal (Healer)",
|
||||
"winter2023RibbonRogueSet": "Ribbon (Rogue)",
|
||||
"winter2023RibbonRogueSet": "Ribbon (Rogue)",
|
||||
"spring2023CaterpillarRogueSet": "Caterpillar (Rogue)",
|
||||
"spring2023HummingbirdWarriorSet": "Hummingbird (Warrior)",
|
||||
"spring2023MoonstoneMageSet": "Moonstone (Mage)",
|
||||
"spring2023LilyHealerSet": "Lily (Healer)",
|
||||
"eventAvailability": "Available for purchase until <%= date(locale) %>.",
|
||||
"eventAvailabilityReturning": "Available for purchase until <%= availableDate(locale) %>. This potion was last available in <%= previousDate(locale) %>.",
|
||||
"dateEndJanuary": "January 31",
|
||||
|
||||
@@ -877,5 +877,15 @@
|
||||
"questVirtualPetRageDescription": "This bar fills when you don't complete your Dailies. When it is full, the Wotchimon will take away some of your party's pending damage!",
|
||||
"questVirtualPetRageEffect": "`Wotchimon uses Bothersome Beep!` Wotchimon sounds a bothersome beep, and its happiness bar suddenly disappears! Pending damage reduced.",
|
||||
"questVirtualPetDropVirtualPetPotion": "Virtual Pet Hatching Potion",
|
||||
"questVirtualPetUnlockText": "Unlocks Virtual Pet Hatching Potion for purchase in the Market"
|
||||
"questVirtualPetUnlockText": "Unlocks Virtual Pet Hatching Potion for purchase in the Market",
|
||||
|
||||
"questPinkMarbleText": "Calm the Corrupted Cupid",
|
||||
"questPinkMarbleNotes": "After hearing rumors about a cave in the Meandering Mountains that has pink rocks and dust shooting out of it, your party starts to investigate. As you approach the cave, there is indeed a huge pink dust cloud – and strangely, you hear a tiny voice's battle cry, followed by the sound of shattering rock.<br><br>@Empress42 accidentally inhales some of the dust and suddenly feels dreamy and less productive. “Same here!” says @QuartzFox, “I'm suddenly fantasizing about a person that I barely know!”<br><br>@a_diamond peeks into the cave and finds a little being zipping around and smashing pink marbled rock to dust. “Take cover! This Cupid has been corrupted and is using his magic to cause limerence and unrealistic infatuations! We have to subdue him!”",
|
||||
"questPinkMarbleCompletion": "You manage to pin the little guy down at last – he was much tougher and faster than expected. Before he stirs again, you take away his quiver of glowing arrows. He blinks and suddenly looks around in surprise. “To escape my own sorrow and heartbreak for a while I pricked myself with one of my arrows… I don't remember anything after that!”<br><br>He is just about to flee the cave, notices that @Loremi has taken a sample of the marble dust and grins. “Try using some of this pink marble dust in a potion! Nurture the pets that hatch from it and you will find that real relationships are born from communication, mutual trust and care. I wish you luck, and I wish you love!”",
|
||||
"questPinkMarbleBoss": "Cupido",
|
||||
"questPinkMarbleRageTitle": "Pink Punch",
|
||||
"questPinkMarbleRageDescription": "This bar fills when you don't complete your Dailies. When it is full, Cupido will take away some of your party's pending damage!",
|
||||
"questPinkMarbleRageEffect": "`Cupido uses Pink Punch!` That wasn't affectionate at all! Your partymates are taken aback. Pending damage reduced.",
|
||||
"questPinkMarbleDropPinkMarblePotion": "Pink Marble Hatching Potion",
|
||||
"QuestPinkMarbleUnlockText": "Unlocks Pink Marble Hatching Potions for purchase in the Market."
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"chatExtensionDesc": "The Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
|
||||
"otherExtensions": "<a target='blank' href='https://habitica.fandom.com/wiki/Extensions,_Add-Ons,_and_Customizations'>Other Extensions</a>",
|
||||
"otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
|
||||
"thirdPartyTools": "Find third party apps, extensions, and all kinds of other tools you can use with your account on the <a href='https://habitica.fandom.com/wiki/Extensions,_Add-Ons,_and_Customizations' target='_blank'>Habitica wiki</a>.",
|
||||
"resetDo": "Do it, reset my account!",
|
||||
"resetComplete": "Reset complete!",
|
||||
"fixValues": "Fix Values",
|
||||
|
||||
@@ -147,6 +147,8 @@
|
||||
"mysterySet202212": "Glacial Guardian Set",
|
||||
"mysterySet202301": "Valiant Vulpine Set",
|
||||
"mysterySet202302": "Trickster Tabby Set",
|
||||
"mysterySet202303": "Mane Character Set",
|
||||
"mysterySet202304": "Tiptop Teapot Set",
|
||||
"mysterySet301404": "Steampunk Standard Set",
|
||||
"mysterySet301405": "Steampunk Accessories Set",
|
||||
"mysterySet301703": "Peacock Steampunk Set",
|
||||
|
||||
@@ -138,5 +138,14 @@
|
||||
"achievementReptacularRumble": "Rumble reptacular",
|
||||
"achievementWoodlandWizard": "Mago del bosque",
|
||||
"achievementWoodlandWizardModalText": "¡Has recogido todas las mascotas del bosque!",
|
||||
"achievementWoodlandWizardText": "Ha incubado todos los colores estándar de las criaturas del bosque: Tejón, Oso, Ciervo, Zorro, Rana, Erizo, Búho, Caracol, Ardilla y Treeling!"
|
||||
"achievementWoodlandWizardText": "Ha incubado todos los colores estándar de las criaturas del bosque: Tejón, Oso, Ciervo, Zorro, Rana, Erizo, Búho, Caracol, Ardilla y Treeling!",
|
||||
"achievementBoneToPick": "Hueso para recoger",
|
||||
"achievementBoneToPickText": "¡Ha eclosionado todas las mascotas Clásicas y de Misión Esqueléticas!",
|
||||
"achievementPolarPro": "Experto Polar",
|
||||
"achievementPolarProModalText": "¡Has coleccionado todas las mascotas Polares!",
|
||||
"achievementBoneToPickModalText": "¡Has coleccionado todas las mascotas clásicas y de misiones esqueléticas!",
|
||||
"achievementPolarProText": "¡Ha eclosionado todos los colores estándar para mascotas Polares: Osos, Zorros, Pinguinos, Ballenas y Lobos!",
|
||||
"achievementPlantParent": "Progenitor de las Plantas",
|
||||
"achievementPlantParentText": "¡Ha eclosionado todos los colores estándar para las mascotas Planta: Cáctus y Esqueje de árbol!",
|
||||
"achievementPlantParentModalText": "¡Has coleccionado todas las Mascotas Planta!"
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"backgrounds062014": "1.ª serie: publicada en junio de 2014",
|
||||
"backgroundBeachText": "Playa",
|
||||
"backgroundBeachNotes": "Relájate en una cálida playa.",
|
||||
"backgroundFairyRingText": "Anillo de hada",
|
||||
"backgroundFairyRingNotes": "Baila en un anillo de hada.",
|
||||
"backgroundFairyRingText": "Anillo de hadas",
|
||||
"backgroundFairyRingNotes": "Baila en un anillo de hadas.",
|
||||
"backgroundForestText": "Bosque",
|
||||
"backgroundForestNotes": "Pasea por un bosque estival.",
|
||||
"backgrounds072014": "2.ª serie: publicada en julio de 2014",
|
||||
@@ -17,7 +17,7 @@
|
||||
"backgroundOpenWatersText": "Aguas abiertas",
|
||||
"backgroundOpenWatersNotes": "Disfruta de las aguas abiertas.",
|
||||
"backgroundSeafarerShipText": "Bajel de marineros",
|
||||
"backgroundSeafarerShipNotes": "Navega a bordo de un barco de marineros.",
|
||||
"backgroundSeafarerShipNotes": "Navega a bordo de un barco marinero.",
|
||||
"backgrounds082014": "3.ª serie: publicada en agosto de 2014",
|
||||
"backgroundCloudsText": "Nubes",
|
||||
"backgroundCloudsNotes": "Planea entre las nubes.",
|
||||
@@ -27,7 +27,7 @@
|
||||
"backgroundVolcanoNotes": "Entra en calor dentro de un volcán.",
|
||||
"backgrounds092014": "4.ª serie: publicada en septiembre de 2014",
|
||||
"backgroundThunderstormText": "Tormenta eléctrica",
|
||||
"backgroundThunderstormNotes": "Dirige un rayo en la tormenta eléctrica.",
|
||||
"backgroundThunderstormNotes": "Conduce rayos en la tormenta eléctrica.",
|
||||
"backgroundAutumnForestText": "Bosque otoñal",
|
||||
"backgroundAutumnForestNotes": "Pasea por un bosque otoñal.",
|
||||
"backgroundHarvestFieldsText": "Campos de cultivo",
|
||||
@@ -675,7 +675,7 @@
|
||||
"backgroundOrangeGroveText": "Naranjal",
|
||||
"backgroundOrangeGroveNotes": "Deambula por un naranjal fragante.",
|
||||
"backgrounds022022": "93.ª serie: publicada en febrero de 2022",
|
||||
"backgrounds032022": "94.ª serie: publiccada en marzo de 2022",
|
||||
"backgrounds032022": "94.ª serie: publicada en marzo de 2022",
|
||||
"backgroundBrickWallWithIvyText": "Pared de Ladrillo con Hiedra",
|
||||
"backgroundBrickWallWithIvyNotes": "Admira una Pared de Ladrillo con Hiedra.",
|
||||
"backgrounds042022": "95ª. serie: publicada en abril de 2022",
|
||||
@@ -689,7 +689,7 @@
|
||||
"backgroundFloweringPrairieText": "Pradera floreciente",
|
||||
"backgroundSpringtimeLakeText": "Lago de Primavera",
|
||||
"backgroundSpringtimeLakeNotes": "Disfruta las vistas a orillas de un Lago de Primavera.",
|
||||
"hideLockedBackgrounds": "Esconde fondos cerrados",
|
||||
"hideLockedBackgrounds": "Ocultar fondos bloqueados",
|
||||
"backgroundBioluminescentWavesText": "Olas Bioluminiscentes",
|
||||
"backgroundBioluminescentWavesNotes": "Admira el resplandor de Olas Bioluminiscentes.",
|
||||
"backgroundUnderwaterCaveNotes": "Explora una Cueva Subacuática.",
|
||||
@@ -724,5 +724,48 @@
|
||||
"backgroundAutumnPicnicText": "Picnic otoñal",
|
||||
"backgroundAutumnPicnicNotes": "Disfruta un picnic otoñal.",
|
||||
"backgroundOldPhotoText": "Foto antigua",
|
||||
"backgroundOldPhotoNotes": "Posa en una foto antigua."
|
||||
"backgroundOldPhotoNotes": "Posa en una foto antigua.",
|
||||
"backgrounds022023": "105ª serie: publicada en febrero del 2023",
|
||||
"backgroundInFrontOfFountainText": "Frente a una fuente",
|
||||
"backgroundInFrontOfFountainNotes": "Pasea frente a una fuente.",
|
||||
"backgroundGoldenBirdcageText": "Jaula de pájaros dorada",
|
||||
"backgroundFancyBedroomText": "Dormitorio elegante",
|
||||
"backgroundFancyBedroomNotes": "Deléitate con un dormitorio elegante.",
|
||||
"backgroundGoldenBirdcageNotes": "Escóndete en una jaula de pájaros dorada.",
|
||||
"backgrounds012023": "104ª serie: publicada en enero del 2023",
|
||||
"backgroundRimeIceText": "Hielo escarchado",
|
||||
"backgroundRimeIceNotes": "Admira el brillante hielo escarchado.",
|
||||
"backgroundSnowyTempleText": "Templo nevado",
|
||||
"backgroundSnowyTempleNotes": "Contempla un sereno templo nevado.",
|
||||
"backgroundWinterLakeWithSwansText": "Lago invernal con cisnes",
|
||||
"backgroundWinterLakeWithSwansNotes": "Disfruta de la naturaleza en un lago invernal con cisnes.",
|
||||
"eventBackgrounds": "Fondos de Evento",
|
||||
"backgroundBirthdayBashText": "Fiesta de cumpleaños",
|
||||
"backgroundBirthdayBashNotes": "Habitica está celebrando una fiesta de cumpleaños, ¡y todos están invitados!",
|
||||
"backgroundMistyAutumnForestText": "Bosque otoñal brumoso",
|
||||
"backgroundMistyAutumnForestNotes": "Camina a través de un brumoso bosque otoñal.",
|
||||
"backgroundAutumnBridgeText": "Puente en otoño",
|
||||
"backgroundAutumnBridgeNotes": "Admira la belleza de un puente en otoño.",
|
||||
"backgrounds102022": "101ª serie: publicada en octubre de 2022",
|
||||
"backgroundSpookyRuinsText": "Ruinas espeluznantes",
|
||||
"backgroundMaskMakersWorkshopNotes": "Prueba una nueva cara en el taller del fabricante de máscaras.",
|
||||
"backgroundMaskMakersWorkshopText": "Taller del fabricante de máscaras",
|
||||
"backgroundCemeteryGateText": "Puerta del cementerio",
|
||||
"backgroundCemeteryGateNotes": "Ronda la puerta del cementerio.",
|
||||
"backgrounds122022": "103ª serie: publicada en diciembre del 2022",
|
||||
"backgroundBranchesOfAHolidayTreeText": "Ramas de un árbol navideño",
|
||||
"backgroundBranchesOfAHolidayTreeNotes": "Retoza en las ramas de un árbol navideño.",
|
||||
"backgroundInsideACrystalText": "Dentro de un cristal",
|
||||
"backgroundInsideACrystalNotes": "Asómate desde el interior desde un cristal.",
|
||||
"backgroundSnowyVillageText": "Pueblo nevado",
|
||||
"backgroundSpookyRuinsNotes": "Explora unas ruinas espeluznantes.",
|
||||
"backgrounds112022": "102ª serie: publicada en noviembre del 2022",
|
||||
"backgroundAmongGiantMushroomsText": "Entre setas gigantes",
|
||||
"backgroundAmongGiantMushroomsNotes": "Maravillate con las setas gigantes.",
|
||||
"backgroundSnowyVillageNotes": "Admira un pueblo nevado.",
|
||||
"backgroundOldTimeyBasketballCourtNotes": "Echa unas canastas en una cancha de baloncesto antigua.",
|
||||
"backgroundMangroveForestText": "Bosque de Manglares",
|
||||
"backgroundMangroveForestNotes": "Explora las lindes del Bosque de Manglares.",
|
||||
"backgrounds032023": "106.ª serie: publicada en marzo de 2023",
|
||||
"backgroundOldTimeyBasketballCourtText": "Cancha de Baloncesto Antigua"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user