mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-21 03:36:48 -05:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52fbb677f9 | |||
| cfaf317b35 | |||
| ca92208c28 | |||
| d2f611bd7e | |||
| 016c43d0d1 | |||
| ea93bfdbde | |||
| c2e8118852 | |||
| 32c99a498f | |||
| f27a0581d9 | |||
| 39d2431b32 | |||
| 235c0da38d | |||
| 9b0800c74b | |||
| 5e1770246d | |||
| a6a18e56d8 | |||
| 7a06263cf4 | |||
| e2e1d1e6be | |||
| 6d2b896bf5 | |||
| 780d67c3de | |||
| aa4d26a5ae | |||
| 1a9707445f | |||
| 3a91fde01d | |||
| dcb3b1633b | |||
| fa43484a1b | |||
| 9712d78fd3 | |||
| f6cc11771f | |||
| 08c51b0908 | |||
| b4aa018699 |
+18
-18
@@ -135,7 +135,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [21.x]
|
||||
mongodb-version: [7.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -145,11 +144,12 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.11.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- name: Start MongoDB 7.0 Replica Set
|
||||
run: |
|
||||
docker run -d --name mongodb -p 27017:27017 mongo:7.0 --replSet rs
|
||||
sleep 5
|
||||
docker exec mongodb mongosh --quiet --eval "rs.initiate({_id: 'rs', members: [{_id: 0, host: 'localhost:27017'}]})"
|
||||
sleep 3
|
||||
|
||||
- run: sudo apt update
|
||||
- run: sudo apt -y install libkrb5-dev
|
||||
@@ -170,7 +170,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [21.x]
|
||||
mongodb-version: [7.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -179,11 +178,12 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.11.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- name: Start MongoDB 7.0 Replica Set
|
||||
run: |
|
||||
docker run -d --name mongodb -p 27017:27017 mongo:7.0 --replSet rs
|
||||
sleep 5
|
||||
docker exec mongodb mongosh --quiet --eval "rs.initiate({_id: 'rs', members: [{_id: 0, host: 'localhost:27017'}]})"
|
||||
sleep 3
|
||||
|
||||
- run: sudo apt update
|
||||
- run: sudo apt -y install libkrb5-dev
|
||||
@@ -204,7 +204,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [21.x]
|
||||
mongodb-version: [7.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -214,11 +213,12 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.11.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- name: Start MongoDB 7.0 Replica Set
|
||||
run: |
|
||||
docker run -d --name mongodb -p 27017:27017 mongo:7.0 --replSet rs
|
||||
sleep 5
|
||||
docker exec mongodb mongosh --quiet --eval "rs.initiate({_id: 'rs', members: [{_id: 0, host: 'localhost:27017'}]})"
|
||||
sleep 3
|
||||
|
||||
- run: sudo apt update
|
||||
- run: sudo apt -y install libkrb5-dev
|
||||
|
||||
Generated
+138
-79
@@ -44,7 +44,7 @@
|
||||
"gulp-filter": "^7.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp.spritesmith": "^6.13.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"habitica-markdown": "github:HabitRPG/habitica-markdown#fiz/emojis-update",
|
||||
"heapdump": "^0.3.15",
|
||||
"helmet": "^4.6.0",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
@@ -105,6 +105,9 @@
|
||||
"npm": "^10"
|
||||
}
|
||||
},
|
||||
"../habitica-markdown/habitica-markdown": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
|
||||
@@ -4167,6 +4170,12 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/apidoc/node_modules/bootstrap": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
|
||||
@@ -4202,6 +4211,15 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/linkify-it": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
|
||||
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -4213,6 +4231,22 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/markdown-it": {
|
||||
"version": "12.3.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
|
||||
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "~2.1.0",
|
||||
"linkify-it": "^3.0.1",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/nodemon": {
|
||||
"version": "2.0.22",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
|
||||
@@ -12497,50 +12531,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/habitica-markdown": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-3.0.0.tgz",
|
||||
"integrity": "sha512-rw1LJ5Vsjx8sfjNa4e2wFuZf5eqqyb5/kfZXPxqfMMgJCCgIhWStDqY3nIclnpGWpemlKd+qbdh2rLiLgm9kng==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "git+ssh://git@github.com/HabitRPG/habitica-markdown.git#204545c1e028f22b937c0a73c5ef250c8973db16",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"habitica-markdown-emoji": "1.2.4",
|
||||
"markdown-it": "10.0.0",
|
||||
"markdown-it-link-attributes": "3.0.0",
|
||||
"markdown-it-linkify-images": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/habitica-markdown-emoji": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/habitica-markdown-emoji/-/habitica-markdown-emoji-1.2.4.tgz",
|
||||
"integrity": "sha512-UV0AxpDToldFQULuhTxC1y4sdNTApaIOh7ZuV/92HCPmCGkv3DAlHtYE67OmCqLVfs26HWAGVJaU3+OEnW3gjg==",
|
||||
"dependencies": {
|
||||
"markdown-it-emoji": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/habitica-markdown/node_modules/entities": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
|
||||
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ=="
|
||||
},
|
||||
"node_modules/habitica-markdown/node_modules/linkify-it": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/habitica-markdown/node_modules/markdown-it": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz",
|
||||
"integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"entities": "~2.0.0",
|
||||
"linkify-it": "^2.0.0",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
"markdown-it": "^14.0.0",
|
||||
"markdown-it-emoji": "^2.0.2",
|
||||
"markdown-it-link-attributes": "^4.0.1",
|
||||
"markdown-it-linkify-images": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
@@ -14552,13 +14550,20 @@
|
||||
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
|
||||
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linkify-it/node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/load-json-file": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||
@@ -14906,59 +14911,79 @@
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "12.3.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
|
||||
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "~2.1.0",
|
||||
"linkify-it": "^3.0.1",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-emoji": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz",
|
||||
"integrity": "sha512-QCz3Hkd+r5gDYtS2xsFXmBYrgw6KuWcJZLCEkdfAuwzZbShCmCfta+hwAMq4NX/4xPzkSHduMKgMkkPUJxSXNg=="
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz",
|
||||
"integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/markdown-it-link-attributes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-3.0.0.tgz",
|
||||
"integrity": "sha512-B34ySxVeo6MuEGSPCWyIYryuXINOvngNZL87Mp7YYfKIf6DcD837+lXA8mo6EBbauKsnGz22ZH0zsbOiQRWTNg=="
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz",
|
||||
"integrity": "sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/markdown-it-linkify-images": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-1.1.1.tgz",
|
||||
"integrity": "sha512-1IEmAaAjIgAwY+tZI0sxDXdy9QKHutj5cN0lH2JBiSZt+2NYKrWRJj0cloQW3OFIfP2MLFA1E+6OLJhXPiLgNw==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-3.0.0.tgz",
|
||||
"integrity": "sha512-Vs5yGJa5MWjFgytzgtn8c1U6RcStj3FZKhhx459U8dYbEE5FTWZ6mMRkYMiDlkFO0j4VCsQT1LT557bY0ETgtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"markdown-it": "^8.4.2"
|
||||
"markdown-it": "^13.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-linkify-images/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/markdown-it-linkify-images/node_modules/entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
|
||||
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-linkify-images/node_modules/linkify-it": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
|
||||
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-linkify-images/node_modules/markdown-it": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz",
|
||||
"integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==",
|
||||
"version": "13.0.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz",
|
||||
"integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"entities": "~1.1.1",
|
||||
"linkify-it": "^2.0.0",
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "~3.0.1",
|
||||
"linkify-it": "^4.0.1",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
},
|
||||
@@ -14969,7 +14994,32 @@
|
||||
"node_modules/markdown-it/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/matchdep": {
|
||||
"version": "2.0.0",
|
||||
@@ -18049,6 +18099,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/q": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@
|
||||
"gulp-filter": "^7.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp.spritesmith": "^6.13.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"habitica-markdown": "^4.0.0",
|
||||
"heapdump": "^0.3.15",
|
||||
"helmet": "^4.6.0",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('GET /export/inbox.html', () => {
|
||||
it('renders the markdown messages as html', async () => {
|
||||
const res = await user.get('/export/inbox.html');
|
||||
|
||||
expect(res).to.include('img class="habitica-emoji"');
|
||||
expect(res).to.include('😄');
|
||||
expect(res).to.include('<h1>Hello!</h1>');
|
||||
expect(res).to.include('<li>list 1</li>');
|
||||
});
|
||||
@@ -46,7 +46,7 @@ describe('GET /export/inbox.html', () => {
|
||||
it('sorts messages from newest to oldest', async () => {
|
||||
const res = await user.get('/export/inbox.html');
|
||||
|
||||
const emojiPosition = res.indexOf('img class="habitica-emoji"');
|
||||
const emojiPosition = res.indexOf('😄');
|
||||
const headingPosition = res.indexOf('<h1>Hello!</h1>');
|
||||
const listPosition = res.indexOf('<li>list 1</li>');
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import md from 'habitica-markdown';
|
||||
|
||||
describe('habiticaMarkdown emoji plugin', () => {
|
||||
it('renders standard emoji as Unicode', () => {
|
||||
const result = md.render(':smile:');
|
||||
expect(result).to.include('😄');
|
||||
expect(result).not.to.include('img');
|
||||
});
|
||||
|
||||
it('renders thumbsup emoji as Unicode', () => {
|
||||
const result = md.render(':thumbsup:');
|
||||
expect(result).to.include('👍');
|
||||
});
|
||||
|
||||
it('renders +1 emoji as Unicode', () => {
|
||||
const result = md.render(':+1:');
|
||||
expect(result).to.include('👍');
|
||||
});
|
||||
|
||||
it('renders melior as an img tag', () => {
|
||||
const result = md.render(':melior:');
|
||||
expect(result).to.include('<img class="habitica-emoji"');
|
||||
expect(result).to.include('src="https://s3.amazonaws.com/habitica-assets/cdn/emoji/melior.png"');
|
||||
expect(result).to.include('alt="melior"');
|
||||
});
|
||||
|
||||
it('does NOT convert emoji inside markdown links', () => {
|
||||
const result = md.render('[:smile: link](http://example.com)');
|
||||
expect(result).to.include(':smile: link');
|
||||
expect(result).not.to.include('😄');
|
||||
});
|
||||
|
||||
it('converts emoji outside of links normally', () => {
|
||||
const result = md.render(':smile: [link](http://example.com)');
|
||||
expect(result).to.include('😄');
|
||||
expect(result).to.include('link');
|
||||
});
|
||||
|
||||
it('leaves removed custom emoji (bowtie) as literal text', () => {
|
||||
const result = md.render(':bowtie:');
|
||||
expect(result).to.include(':bowtie:');
|
||||
expect(result).not.to.include('img');
|
||||
});
|
||||
|
||||
it('leaves unknown shortcodes as literal text', () => {
|
||||
const result = md.render(':nonexistent_emoji_xyz:');
|
||||
expect(result).to.include(':nonexistent_emoji_xyz:');
|
||||
});
|
||||
|
||||
it('renders new emoji not in the old dataset', () => {
|
||||
const result = md.render(':yawning_face:');
|
||||
expect(result).to.include('🥱');
|
||||
});
|
||||
|
||||
it('supports unsafeHTMLRender', () => {
|
||||
const result = md.unsafeHTMLRender('<b>bold</b> :smile:');
|
||||
expect(result).to.include('<b>bold</b>');
|
||||
expect(result).to.include('😄');
|
||||
});
|
||||
|
||||
it('supports renderWithMentions', () => {
|
||||
const result = md.renderWithMentions(':smile: @testuser', { userName: 'testuser' });
|
||||
expect(result).to.include('😄');
|
||||
expect(result).to.include('at-text');
|
||||
expect(result).to.include('at-highlight');
|
||||
});
|
||||
});
|
||||
@@ -28,7 +28,7 @@
|
||||
"eslint-config-habitrpg": "6.2.0",
|
||||
"eslint-plugin-mocha": "5.3.0",
|
||||
"eslint-plugin-vue": "7.20.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"habitica-markdown": "^4.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"intro.js": "^7.2.0",
|
||||
"jquery": "^3.7.1",
|
||||
|
||||
@@ -229,6 +229,11 @@ export default {
|
||||
}
|
||||
return Promise.resolve(error);
|
||||
}
|
||||
if (error.response.status === 404
|
||||
&& error.response.config.method === 'get'
|
||||
&& error.response.config.url.indexOf('/api/v4/groups/party') !== -1) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
const errorData = error.response.data;
|
||||
|
||||
@@ -58,6 +58,11 @@ h3.markdown {
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.emoji-native {
|
||||
font-size: 0.85em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 0 16px;
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="searchResults.length > 0"
|
||||
class="autocomplete-selection"
|
||||
:style="autocompleteStyle"
|
||||
>
|
||||
<div
|
||||
v-for="result in searchResults"
|
||||
:key="result.shortcode"
|
||||
class="autocomplete-results d-flex align-items-center"
|
||||
:class="{'hover-background': result.hover}"
|
||||
@click="select(result)"
|
||||
@mouseenter="setHover(result)"
|
||||
@mouseleave="resetSelection()"
|
||||
>
|
||||
<span class="emoji-char">{{ result.emoji }}</span>
|
||||
<span
|
||||
class="shortcode ml-2"
|
||||
:class="{'hover-foreground': result.hover}"
|
||||
>:{{ result.shortcode }}:</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.autocomplete-results {
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.autocomplete-selection {
|
||||
box-shadow: 1px 1px 1px #efefef;
|
||||
}
|
||||
|
||||
.hover-background {
|
||||
background-color: rgba(213, 200, 255, 0.32);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hover-foreground {
|
||||
color: $purple-300 !important;
|
||||
}
|
||||
|
||||
.emoji-char {
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.shortcode {
|
||||
color: $gray-200;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import emojiDefs from 'markdown-it-emoji/lib/data/full.json';
|
||||
|
||||
export default {
|
||||
props: ['text', 'caretPosition', 'coords', 'textbox'],
|
||||
data () {
|
||||
return {
|
||||
colonRegex: /:([a-zA-Z0-9_+]*)$/,
|
||||
currentSearch: '',
|
||||
searchActive: false,
|
||||
searchResults: [],
|
||||
selected: null,
|
||||
emojiList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
autocompleteStyle () {
|
||||
const top = this.textbox.offsetTop + this.textbox.offsetHeight + 2;
|
||||
return {
|
||||
top: `${top}px`,
|
||||
left: `${this.textbox.offsetLeft}px`,
|
||||
position: 'absolute',
|
||||
minWidth: '150px',
|
||||
zIndex: 100,
|
||||
backgroundColor: 'white',
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
text (newText, prevText) {
|
||||
if (!this.textbox) return;
|
||||
const delCharsBool = prevText.length > newText.length;
|
||||
const caretPosition = this.textbox.selectionEnd;
|
||||
const lastFocusChar = delCharsBool ? prevText[caretPosition] : newText[caretPosition - 1];
|
||||
if (
|
||||
newText.length === 0
|
||||
|| (lastFocusChar === ':' && delCharsBool)
|
||||
) {
|
||||
this.cancel();
|
||||
} else {
|
||||
if (lastFocusChar === ':') this.searchActive = true;
|
||||
if (this.searchActive) {
|
||||
this.searchResults = this.solveSearchResults(newText.substring(0, caretPosition));
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
created () {
|
||||
const list = [];
|
||||
const keys = Object.keys(emojiDefs);
|
||||
keys.sort();
|
||||
for (const key of keys) {
|
||||
list.push({ shortcode: key, emoji: emojiDefs[key], hover: false });
|
||||
}
|
||||
this.emojiList = list;
|
||||
},
|
||||
methods: {
|
||||
solveSearchResults (textFocus) {
|
||||
const regexRes = this.colonRegex.exec(textFocus);
|
||||
if (!regexRes) {
|
||||
this.cancel();
|
||||
return [];
|
||||
}
|
||||
this.currentSearch = regexRes[1];
|
||||
|
||||
if (this.currentSearch.length === 0) return [];
|
||||
|
||||
const lowerSearch = this.currentSearch.toLowerCase();
|
||||
return this.emojiList
|
||||
.filter(entry => entry.shortcode.startsWith(lowerSearch))
|
||||
.slice(0, 6)
|
||||
.map(entry => ({ ...entry, hover: false }));
|
||||
},
|
||||
select (result) {
|
||||
const { text } = this;
|
||||
const targetName = `${result.shortcode}: `;
|
||||
const oldCaret = this.caretPosition;
|
||||
const escapedSearch = this.currentSearch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
let newText = text.substring(0, this.caretPosition)
|
||||
.replace(new RegExp(`${escapedSearch}$`), targetName);
|
||||
const newCaret = newText.length;
|
||||
newText += text.substring(oldCaret, text.length);
|
||||
this.$emit('select', newText, newCaret);
|
||||
|
||||
this.cancel();
|
||||
},
|
||||
setHover (result) {
|
||||
this.resetSelection();
|
||||
result.hover = true;
|
||||
},
|
||||
clearHover () {
|
||||
for (const selection of this.searchResults) {
|
||||
selection.hover = false;
|
||||
}
|
||||
},
|
||||
resetSelection () {
|
||||
this.clearHover();
|
||||
this.selected = null;
|
||||
},
|
||||
selectNext () {
|
||||
if (this.searchResults.length > 0) {
|
||||
this.clearHover();
|
||||
this.selected = this.selected === null
|
||||
? 0
|
||||
: (this.selected + 1) % this.searchResults.length;
|
||||
this.searchResults[this.selected].hover = true;
|
||||
}
|
||||
},
|
||||
selectPrevious () {
|
||||
if (this.searchResults.length > 0) {
|
||||
this.clearHover();
|
||||
this.selected = this.selected === null
|
||||
? this.searchResults.length - 1
|
||||
: (this.selected - 1 + this.searchResults.length) % this.searchResults.length;
|
||||
this.searchResults[this.selected].hover = true;
|
||||
}
|
||||
},
|
||||
makeSelection () {
|
||||
if (this.searchResults.length > 0 && this.selected !== null) {
|
||||
const result = this.searchResults[this.selected];
|
||||
this.select(result);
|
||||
}
|
||||
},
|
||||
cancel () {
|
||||
this.searchActive = false;
|
||||
this.searchResults = [];
|
||||
this.resetSelection();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,577 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="group-plan-selection"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
size="md"
|
||||
@show="loadData"
|
||||
@hide="onHide"
|
||||
>
|
||||
<div class="selection-modal">
|
||||
<div class="modal-header-row">
|
||||
<h2 class="title">
|
||||
{{ $t('chooseAnOption') }}
|
||||
</h2>
|
||||
<div class="header-actions">
|
||||
<span
|
||||
class="cancel-text"
|
||||
@click="close"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</span>
|
||||
<button
|
||||
class="btn btn-primary next-button"
|
||||
:class="{ disabled: !selectedOption }"
|
||||
:disabled="!selectedOption"
|
||||
@click="continueFlow"
|
||||
>
|
||||
{{ $t('next') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="loading"
|
||||
class="loading-container"
|
||||
>
|
||||
<div class="spinner-border text-secondary"></div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="hasUpgradeableGroups"
|
||||
class="section-header"
|
||||
>
|
||||
{{ $t('upgradeExistingGroup') }}
|
||||
</div>
|
||||
|
||||
<selectable-card
|
||||
v-for="group in upgradeableGuilds"
|
||||
:key="group._id"
|
||||
class="option-card"
|
||||
:selected="isSelected(group)"
|
||||
@click="selectOption(group)"
|
||||
>
|
||||
<div class="option-content">
|
||||
<div class="option-info">
|
||||
<div class="option-name">
|
||||
{{ group.name }}
|
||||
</div>
|
||||
<div class="option-members">
|
||||
{{ formatMemberCount(group.memberCount) }}
|
||||
</div>
|
||||
<div class="option-label previously-upgraded">
|
||||
<div
|
||||
class="svg-icon sparkle-icon"
|
||||
v-html="icons.sparkles"
|
||||
></div>
|
||||
{{ $t('previouslyUpgradedGroup') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-price">
|
||||
${{ calculatePrice(group.memberCount) }}.00/mo
|
||||
</div>
|
||||
</div>
|
||||
</selectable-card>
|
||||
|
||||
<selectable-card
|
||||
v-if="upgradeableParty"
|
||||
class="option-card"
|
||||
:class="{ 'has-pending-warning': partyPendingInviteCount > 0 }"
|
||||
:selected="isSelected(upgradeableParty)"
|
||||
@click="selectOption(upgradeableParty)"
|
||||
>
|
||||
<div class="option-content">
|
||||
<div class="option-info">
|
||||
<div class="option-name">
|
||||
{{ upgradeableParty.name }}
|
||||
</div>
|
||||
<div class="option-members">
|
||||
{{ formatMemberCount(upgradeableParty.memberCount) }}
|
||||
<span
|
||||
v-if="partyPendingInviteCount > 0"
|
||||
class="pending-count"
|
||||
>
|
||||
{{ $t('pendingCount', { count: partyPendingInviteCount }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="isPartyPreviouslyUpgraded"
|
||||
class="option-label previously-upgraded"
|
||||
>
|
||||
<div
|
||||
class="svg-icon sparkle-icon"
|
||||
v-html="icons.sparkles"
|
||||
></div>
|
||||
{{ $t('previouslyUpgradedGroup') }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="option-label your-party"
|
||||
>
|
||||
<div
|
||||
class="svg-icon member-icon"
|
||||
v-html="icons.member"
|
||||
></div>
|
||||
{{ $t('yourParty') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-price">
|
||||
${{ calculatePrice(upgradeableParty.memberCount) }}.00/mo
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="partyPendingInviteCount > 0"
|
||||
class="pending-warning-banner"
|
||||
>
|
||||
<div
|
||||
class="svg-icon alert-icon"
|
||||
v-html="icons.alert"
|
||||
></div>
|
||||
<span class="warning-text">{{ $t('upgradeCancelsPendingInvites') }}</span>
|
||||
</div>
|
||||
</selectable-card>
|
||||
|
||||
<div
|
||||
v-if="hasUpgradeableGroups"
|
||||
class="or-divider"
|
||||
>
|
||||
<div class="divider-line"></div>
|
||||
<span class="or-text">{{ $t('or') }}</span>
|
||||
<div class="divider-line"></div>
|
||||
</div>
|
||||
|
||||
<selectable-card
|
||||
class="option-card create-new"
|
||||
:selected="selectedOption === 'new'"
|
||||
@click="selectOption('new')"
|
||||
>
|
||||
<div class="option-content">
|
||||
<div class="option-info">
|
||||
<div class="option-name">
|
||||
{{ $t('createNewGroup') }}
|
||||
</div>
|
||||
<div class="option-description">
|
||||
{{ $t('inviteOthersForAdditional') }}
|
||||
<span class="price-highlight">${{ perMemberPrice }}.00</span>
|
||||
{{ $t('perMember') }}.
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-price">
|
||||
${{ basePrice }}.00/mo
|
||||
</div>
|
||||
</div>
|
||||
</selectable-card>
|
||||
|
||||
<div class="footer-note">
|
||||
{{ $t('additionalMembersProrated') }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.selection-modal {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.modal-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
color: $purple-200;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cancel-text {
|
||||
color: $blue-10;
|
||||
font-size: 0.875rem;
|
||||
margin-right: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.next-button {
|
||||
min-width: 64px;
|
||||
|
||||
&.disabled {
|
||||
background-color: $gray-300;
|
||||
border-color: $gray-300;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
color: $gray-10;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.option-card {
|
||||
margin-bottom: 12px;
|
||||
|
||||
::v-deep .option-name {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
&.selected ::v-deep .option-name {
|
||||
color: $purple-200;
|
||||
}
|
||||
}
|
||||
|
||||
.pending-warning-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
background-color: $yellow-50;
|
||||
border-radius: 0 0 6px 6px;
|
||||
margin: 16px -16px 0 -16px;
|
||||
gap: 4px;
|
||||
|
||||
.selected & {
|
||||
margin: 15px -15px 0 -15px;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
::v-deep path {
|
||||
fill: $gray-10;
|
||||
}
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: $gray-10;
|
||||
}
|
||||
}
|
||||
|
||||
.option-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding-left: 32px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.option-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.option-name {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.option-members {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: $gray-100;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.pending-count {
|
||||
font-weight: 700;
|
||||
color: $yellow-5;
|
||||
}
|
||||
}
|
||||
|
||||
.option-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
gap: 4px;
|
||||
|
||||
&.previously-upgraded {
|
||||
font-weight: 700;
|
||||
color: $blue-10;
|
||||
}
|
||||
|
||||
&.your-party {
|
||||
font-weight: 700;
|
||||
color: $gray-100;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.sparkle-icon {
|
||||
color: $blue-10;
|
||||
}
|
||||
|
||||
.member-icon {
|
||||
color: $gray-100;
|
||||
|
||||
::v-deep path {
|
||||
fill: $gray-100;
|
||||
stroke: $gray-100;
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option-description {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: $gray-100;
|
||||
|
||||
.price-highlight {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.option-price {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: $purple-200;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.or-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
|
||||
.divider-line {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background-color: $gray-500;
|
||||
}
|
||||
|
||||
.or-text {
|
||||
padding: 0 16px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: $gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
.create-new {
|
||||
.option-name {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: $gray-100;
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
#group-plan-selection {
|
||||
.modal-dialog {
|
||||
max-width: 504px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 14px 28px 0 rgba(26, 24, 29, 0.24), 0 10px 10px 0 rgba(26, 24, 29, 0.28);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.option-card.has-pending-warning.selectable-card {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import paymentsMixin from '@/mixins/payments';
|
||||
import { mapState } from '@/libs/store';
|
||||
import SelectableCard from '@/components/ui/selectableCard.vue';
|
||||
import svgSparkles from '@/assets/svg/sparkles.svg?raw';
|
||||
import svgMember from '@/assets/svg/member-icon.svg?raw';
|
||||
import svgAlert from '@/assets/svg/for-css/alert.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SelectableCard,
|
||||
},
|
||||
mixins: [paymentsMixin],
|
||||
data () {
|
||||
return {
|
||||
selectedOption: null,
|
||||
userGuilds: [],
|
||||
userParty: null,
|
||||
activeGroupPlanIds: [],
|
||||
loading: true,
|
||||
basePrice: 9,
|
||||
perMemberPrice: 3,
|
||||
icons: Object.freeze({
|
||||
sparkles: svgSparkles,
|
||||
member: svgMember,
|
||||
alert: svgAlert,
|
||||
}),
|
||||
partyPendingInviteCount: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
upgradeableGuilds () {
|
||||
return this.userGuilds.filter(group => {
|
||||
const leaderId = group.leader?._id || group.leader;
|
||||
if (leaderId !== this.user._id) return false;
|
||||
const purchased = group.purchased;
|
||||
if (!purchased?.wasUpgraded) return false;
|
||||
if (this.activeGroupPlanIds.includes(group._id)) return false;
|
||||
if (!purchased.dateTerminated) return false;
|
||||
return new Date(purchased.dateTerminated) < new Date();
|
||||
});
|
||||
},
|
||||
upgradeableParty () {
|
||||
if (!this.userParty) return null;
|
||||
|
||||
const leaderId = this.userParty.leader?._id || this.userParty.leader;
|
||||
if (leaderId !== this.user._id) return null;
|
||||
|
||||
if (this.activeGroupPlanIds.includes(this.userParty._id)) return null;
|
||||
|
||||
return this.userParty;
|
||||
},
|
||||
hasUpgradeableGroups () {
|
||||
return this.upgradeableGuilds.length > 0 || this.upgradeableParty !== null;
|
||||
},
|
||||
isPartyPreviouslyUpgraded () {
|
||||
if (!this.userParty) return false;
|
||||
const purchased = this.userParty.purchased;
|
||||
if (!purchased?.wasUpgraded) return false;
|
||||
if (!purchased.dateTerminated) return false;
|
||||
return new Date(purchased.dateTerminated) < new Date();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async loadData () {
|
||||
this.loading = true;
|
||||
this.selectedOption = null;
|
||||
this.partyPendingInviteCount = 0;
|
||||
|
||||
try {
|
||||
const [guildsResponse, partyResponse] = await Promise.all([
|
||||
axios.get('/api/v4/groups', { params: { type: 'guilds', includeExpiredPlans: 'true' } }),
|
||||
axios.get('/api/v4/groups/party').catch(() => ({ data: { data: null } })),
|
||||
]);
|
||||
|
||||
this.userGuilds = guildsResponse.data.data || [];
|
||||
this.userParty = partyResponse.data.data;
|
||||
|
||||
if (this.userParty) {
|
||||
try {
|
||||
const invitesResponse = await axios.get(`/api/v4/groups/${this.userParty._id}/invites`);
|
||||
this.partyPendingInviteCount = invitesResponse.data.data?.length || 0;
|
||||
} catch (e) {
|
||||
this.partyPendingInviteCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
await this.$store.dispatch('guilds:getGroupPlans', true);
|
||||
const groupPlans = this.$store.state.groupPlans?.data || [];
|
||||
this.activeGroupPlanIds = groupPlans.map(g => g._id);
|
||||
} catch (e) {
|
||||
console.error('Error loading group data:', e);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.upgradeableGuilds.length > 0) {
|
||||
this.selectedOption = this.upgradeableGuilds[0];
|
||||
} else if (this.upgradeableParty) {
|
||||
this.selectedOption = this.upgradeableParty;
|
||||
} else {
|
||||
this.selectedOption = 'new';
|
||||
}
|
||||
});
|
||||
},
|
||||
selectOption (option) {
|
||||
this.selectedOption = option;
|
||||
},
|
||||
isSelected (group) {
|
||||
if (!this.selectedOption || this.selectedOption === 'new') return false;
|
||||
return this.selectedOption._id === group._id;
|
||||
},
|
||||
calculatePrice (memberCount) {
|
||||
return this.basePrice + (this.perMemberPrice * (memberCount - 1));
|
||||
},
|
||||
formatMemberCount (count) {
|
||||
return count === 1 ? this.$t('oneMember') : this.$t('membersCount', { count });
|
||||
},
|
||||
continueFlow () {
|
||||
if (!this.selectedOption) return;
|
||||
|
||||
const selection = this.selectedOption;
|
||||
this.close();
|
||||
|
||||
if (selection === 'new') {
|
||||
this.$root.$emit('bv::show::modal', 'create-group');
|
||||
} else {
|
||||
this.stripeGroup({ group: selection, upgrade: true });
|
||||
}
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'group-plan-selection');
|
||||
},
|
||||
onHide () {
|
||||
this.selectedOption = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -41,6 +41,14 @@
|
||||
:chat="group.chat"
|
||||
@select="selectedAutocomplete"
|
||||
/>
|
||||
<emoji-auto-complete
|
||||
ref="emojiAutocomplete"
|
||||
:text="newMessage"
|
||||
:textbox="textbox"
|
||||
:coords="mixinData.autoComplete.coords"
|
||||
:caret-position="mixinData.autoComplete.caretPosition"
|
||||
@select="selectedAutocomplete"
|
||||
/>
|
||||
</div>
|
||||
<community-guidelines />
|
||||
<div class="row chat-actions">
|
||||
@@ -90,6 +98,7 @@ import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import autocomplete from '../chat/autoComplete';
|
||||
import emojiAutoComplete from '../chat/emojiAutoComplete';
|
||||
import communityGuidelines from './communityGuidelines';
|
||||
import chatMessages from '../chat/chatMessages';
|
||||
import { mapState } from '@/libs/store';
|
||||
@@ -102,6 +111,7 @@ export default {
|
||||
},
|
||||
components: {
|
||||
autocomplete,
|
||||
emojiAutoComplete,
|
||||
communityGuidelines,
|
||||
chatMessages,
|
||||
},
|
||||
|
||||
@@ -25,53 +25,61 @@
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="row icon-row">
|
||||
<div
|
||||
class="item-with-icon"
|
||||
class="item-with-icon p-2"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
@keyup.enter="showMemberModal()"
|
||||
@click="showMemberModal()"
|
||||
>
|
||||
<div
|
||||
v-if="group.memberCount > 1000"
|
||||
class="svg-icon shield"
|
||||
v-html="icons.goldGuildBadgeIcon"
|
||||
></div>
|
||||
<div
|
||||
v-if="group.memberCount > 100 && group.memberCount < 999"
|
||||
class="svg-icon shield"
|
||||
v-html="icons.silverGuildBadgeIcon"
|
||||
></div>
|
||||
<div
|
||||
v-if="group.memberCount < 100"
|
||||
class="svg-icon shield"
|
||||
v-html="icons.bronzeGuildBadgeIcon"
|
||||
></div>
|
||||
<span class="number">{{ group.memberCount | abbrNum }}</span>
|
||||
<div
|
||||
v-once
|
||||
class="member-list label"
|
||||
>
|
||||
{{ $t('memberList') }}
|
||||
<div class="box-content">
|
||||
<div class="icon-number-row">
|
||||
<div
|
||||
v-if="group.memberCount > 1000"
|
||||
class="svg-icon shield"
|
||||
v-html="icons.goldGuildBadgeIcon"
|
||||
></div>
|
||||
<div
|
||||
v-if="group.memberCount > 100 && group.memberCount < 999"
|
||||
class="svg-icon shield"
|
||||
v-html="icons.silverGuildBadgeIcon"
|
||||
></div>
|
||||
<div
|
||||
v-if="group.memberCount < 100"
|
||||
class="svg-icon shield"
|
||||
v-html="icons.bronzeGuildBadgeIcon"
|
||||
></div>
|
||||
<span class="number">{{ group.memberCount | abbrNum }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('memberList') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isParty">
|
||||
<div
|
||||
class="item-with-icon"
|
||||
class="item-with-icon p-2"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
@keyup.enter="showGroupGems()"
|
||||
@click="showGroupGems()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon gem"
|
||||
v-html="icons.gem"
|
||||
></div>
|
||||
<span class="number">{{ group.balance * 4 }}</span>
|
||||
<div
|
||||
v-once
|
||||
class="label"
|
||||
>
|
||||
{{ $t('guildBank') }}
|
||||
<div class="box-content">
|
||||
<div class="icon-number-row">
|
||||
<div
|
||||
class="svg-icon gem"
|
||||
v-html="icons.gem"
|
||||
></div>
|
||||
<span class="number">{{ group.balance * 4 }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('guildBank') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,35 +136,57 @@
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
margin-left: 1em;
|
||||
width: 120px;
|
||||
height: 76px;
|
||||
margin-right: 1rem;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
vertical-align: bottom;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&:last-of-type {
|
||||
margin-left: 0.5rem;
|
||||
.box-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.svg-icon.shield, .svg-icon.gem {
|
||||
width: 28px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
.icon-number-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.1em;
|
||||
|
||||
.number {
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-top: .5em;
|
||||
.details {
|
||||
font-size: 11px;
|
||||
color: $gray-200;
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
line-height: 1.1;
|
||||
word-break: break-word;
|
||||
max-height: 2.2em;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,11 +245,6 @@
|
||||
.icon-row {
|
||||
margin-top: 1em;
|
||||
justify-content: flex-end;
|
||||
|
||||
.number {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-row {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<group-plan-selection-modal />
|
||||
<group-plan-creation-modal />
|
||||
<div class="d-flex justify-content-center">
|
||||
<div
|
||||
@@ -315,10 +316,12 @@
|
||||
import { setup as setupPayments } from '@/libs/payments';
|
||||
import paymentsMixin from '../../mixins/payments';
|
||||
import GroupPlanCreationModal from '../group-plans/groupPlanCreationModal.vue';
|
||||
import GroupPlanSelectionModal from '../group-plans/groupPlanSelectionModal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GroupPlanCreationModal,
|
||||
GroupPlanSelectionModal,
|
||||
},
|
||||
mixins: [paymentsMixin],
|
||||
data () {
|
||||
@@ -359,7 +362,7 @@ export default {
|
||||
if (this.upgradingGroup._id) {
|
||||
return this.stripeGroup({ group: this.upgradingGroup, upgrade: true });
|
||||
}
|
||||
return this.$root.$emit('bv::show::modal', 'create-group');
|
||||
return this.$root.$emit('bv::show::modal', 'group-plan-selection');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -70,6 +70,13 @@
|
||||
spellcheck="true"
|
||||
:disabled="challengeAccessRequired"
|
||||
:placeholder="$t('addATitle')"
|
||||
@focus="setActiveField('title')"
|
||||
@keydown="autoCompleteMixinUpdateCarretPosition"
|
||||
@keydown.tab="autoCompleteMixinHandleTab($event)"
|
||||
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
|
||||
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
|
||||
@keypress.enter="titleEnterHandler($event)"
|
||||
@keydown.esc="autoCompleteMixinHandleEscape($event)"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
@@ -92,11 +99,27 @@
|
||||
</small>
|
||||
</div>
|
||||
<textarea
|
||||
ref="notesTextarea"
|
||||
v-model="task.notes"
|
||||
class="form-control input-notes"
|
||||
:class="cssClass('input')"
|
||||
:placeholder="$t('addNotes')"
|
||||
@focus="setActiveField('notes')"
|
||||
@keydown="autoCompleteMixinUpdateCarretPosition"
|
||||
@keydown.tab="autoCompleteMixinHandleTab($event)"
|
||||
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
|
||||
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
|
||||
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
|
||||
@keydown.esc="autoCompleteMixinHandleEscape($event)"
|
||||
></textarea>
|
||||
<emoji-auto-complete
|
||||
ref="emojiAutocomplete"
|
||||
:text="activeFieldText"
|
||||
:textbox="textbox"
|
||||
:coords="mixinData.autoComplete.coords"
|
||||
:caret-position="mixinData.autoComplete.caretPosition"
|
||||
@select="selectedAutocomplete"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -712,6 +735,7 @@
|
||||
}
|
||||
|
||||
.task-modal-header {
|
||||
position: relative;
|
||||
color: $white;
|
||||
width: 100%;
|
||||
border-top-left-radius: 8px;
|
||||
@@ -1160,6 +1184,8 @@ import lockableLabel from '@/components/tasks/modal-controls/lockableLabel';
|
||||
import selectList from '@/components/ui/selectList';
|
||||
|
||||
import syncTask from '../../mixins/syncTask';
|
||||
import emojiAutoComplete from '@/components/chat/emojiAutoComplete';
|
||||
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
|
||||
|
||||
import positiveIcon from '@/assets/svg/positive.svg?raw';
|
||||
import negativeIcon from '@/assets/svg/negative.svg?raw';
|
||||
@@ -1182,15 +1208,18 @@ export default {
|
||||
toggleCheckbox,
|
||||
lockableLabel,
|
||||
selectList,
|
||||
emojiAutoComplete,
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
mixins: [syncTask],
|
||||
mixins: [syncTask, autoCompleteHelperMixin],
|
||||
// purpose is either create or edit, task is the task created or edited
|
||||
props: ['task', 'purpose', 'challengeId', 'groupId'],
|
||||
data () {
|
||||
return {
|
||||
textbox: null,
|
||||
activeField: 'title',
|
||||
showAssignedSelect: false,
|
||||
newChecklistItem: null,
|
||||
icons: Object.freeze({
|
||||
@@ -1314,6 +1343,10 @@ export default {
|
||||
selectedTags () {
|
||||
return this.getTagsFor(this.task);
|
||||
},
|
||||
activeFieldText () {
|
||||
if (!this.task) return '';
|
||||
return this.activeField === 'title' ? (this.task.text || '') : (this.task.notes || '');
|
||||
},
|
||||
showStatAssignment () {
|
||||
return this.task.type !== 'reward'
|
||||
&& !this.groupId
|
||||
@@ -1489,6 +1522,35 @@ export default {
|
||||
},
|
||||
focusInput () {
|
||||
this.$refs.inputToFocus.focus();
|
||||
this.setActiveField('title');
|
||||
},
|
||||
setActiveField (field) {
|
||||
this.activeField = field;
|
||||
if (field === 'title') {
|
||||
this.textbox = this.$refs.inputToFocus;
|
||||
} else {
|
||||
this.textbox = this.$refs.notesTextarea;
|
||||
}
|
||||
},
|
||||
titleEnterHandler (e) {
|
||||
const ac = this._getActiveAutocomplete();
|
||||
if (ac && ac.selected !== null) {
|
||||
e.preventDefault();
|
||||
ac.makeSelection();
|
||||
} else if (ac) {
|
||||
ac.cancel();
|
||||
}
|
||||
},
|
||||
selectedAutocomplete (newText, newCaret) {
|
||||
if (this.activeField === 'title') {
|
||||
this.task.text = newText;
|
||||
} else {
|
||||
this.task.notes = newText;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.textbox.setSelectionRange(newCaret, newCaret);
|
||||
this.textbox.focus();
|
||||
});
|
||||
},
|
||||
async addTag (name) {
|
||||
const tagResult = await this.createTag({ name });
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div
|
||||
class="selectable-card"
|
||||
:class="{ selected }"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div
|
||||
v-if="selected"
|
||||
class="checkmark-corner"
|
||||
>
|
||||
<div
|
||||
class="svg-icon check-icon"
|
||||
v-html="icons.check"
|
||||
></div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.selectable-card {
|
||||
position: relative;
|
||||
background: $white;
|
||||
border: 1px solid $gray-400;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 1px 2px 0px rgba(26, 24, 29, 0.08);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 3px 6px 0px rgba(26, 24, 29, 0.16), 0px 3px 6px 0px rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border: 2px solid $purple-300;
|
||||
padding: 15px;
|
||||
box-shadow: 0px 3px 6px 0px rgba(26, 24, 29, 0.16), 0px 3px 6px 0px rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
}
|
||||
|
||||
.checkmark-corner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-style: solid;
|
||||
border-width: 48px 48px 0 0;
|
||||
border-color: $purple-300 transparent transparent transparent;
|
||||
border-radius: 6px 0 0 0;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import svgCheck from '@/assets/svg/check.svg?raw';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
check: svgCheck,
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -398,14 +398,29 @@
|
||||
:placeholder="$t('imageUrl')"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group" style="position: relative;">
|
||||
<label>{{ $t('about') }}</label>
|
||||
<textarea
|
||||
ref="blurbTextarea"
|
||||
v-model="editingProfile.blurb"
|
||||
class="form-control"
|
||||
rows="5"
|
||||
:placeholder="$t('displayBlurbPlaceholder')"
|
||||
@keydown="autoCompleteMixinUpdateCarretPosition"
|
||||
@keydown.tab="autoCompleteMixinHandleTab($event)"
|
||||
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
|
||||
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
|
||||
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
|
||||
@keydown.esc="autoCompleteMixinHandleEscape($event)"
|
||||
></textarea>
|
||||
<emoji-auto-complete
|
||||
ref="emojiAutocomplete"
|
||||
:text="editingProfile.blurb"
|
||||
:textbox="textbox"
|
||||
:coords="mixinData.autoComplete.coords"
|
||||
:caret-position="mixinData.autoComplete.caretPosition"
|
||||
@select="selectedAutocomplete"
|
||||
/>
|
||||
<!-- include ../../shared/formatting-help-->
|
||||
</div>
|
||||
</div>
|
||||
@@ -1001,6 +1016,8 @@ import mute from '@/assets/svg/mute.svg?raw';
|
||||
import shadowMute from '@/assets/svg/shadow-mute.svg?raw';
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
import { userCustomStateMixin } from '../../mixins/userState';
|
||||
import emojiAutoComplete from '@/components/chat/emojiAutoComplete';
|
||||
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
|
||||
// @TODO: EMAILS.COMMUNITY_MANAGER_EMAIL
|
||||
const COMMUNITY_MANAGER_EMAIL = 'admin@habitica.com';
|
||||
|
||||
@@ -1012,8 +1029,9 @@ export default {
|
||||
MemberDetails,
|
||||
profileStats,
|
||||
toggleSwitch,
|
||||
emojiAutoComplete,
|
||||
},
|
||||
mixins: [externalLinks, userCustomStateMixin('userLoggedIn')],
|
||||
mixins: [externalLinks, userCustomStateMixin('userLoggedIn'), autoCompleteHelperMixin],
|
||||
props: ['userId', 'startingPage'],
|
||||
data () {
|
||||
return {
|
||||
@@ -1033,6 +1051,7 @@ export default {
|
||||
mute,
|
||||
shadowMute,
|
||||
}),
|
||||
textbox: null,
|
||||
userIdToMessage: '',
|
||||
editing: false,
|
||||
editingProfile: {
|
||||
@@ -1121,6 +1140,13 @@ export default {
|
||||
userLoggedIn () {
|
||||
this.loadUser();
|
||||
},
|
||||
editing (val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
this.textbox = this.$refs.blurbTextarea;
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.loadUser();
|
||||
@@ -1331,6 +1357,13 @@ export default {
|
||||
this.$emit('toggled', this.isOpened);
|
||||
},
|
||||
|
||||
selectedAutocomplete (newText, newCaret) {
|
||||
this.editingProfile.blurb = newText;
|
||||
this.$nextTick(() => {
|
||||
this.textbox.setSelectionRange(newCaret, newCaret);
|
||||
this.textbox.focus();
|
||||
});
|
||||
},
|
||||
reportPlayer () {
|
||||
this.$root.$emit('habitica::report-profile', {
|
||||
memberId: this.user._id,
|
||||
|
||||
@@ -15,46 +15,60 @@ export const autoCompleteHelperMixin = {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
_getActiveAutocomplete () {
|
||||
if (this.$refs.autocomplete && this.$refs.autocomplete.searchActive) {
|
||||
return this.$refs.autocomplete;
|
||||
}
|
||||
if (this.$refs.emojiAutocomplete && this.$refs.emojiAutocomplete.searchActive) {
|
||||
return this.$refs.emojiAutocomplete;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
autoCompleteMixinHandleTab (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
const ac = this._getActiveAutocomplete();
|
||||
if (ac) {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) {
|
||||
this.$refs.autocomplete.selectPrevious();
|
||||
ac.selectPrevious();
|
||||
} else {
|
||||
this.$refs.autocomplete.selectNext();
|
||||
ac.selectNext();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
autoCompleteMixinHandleEscape (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
const ac = this._getActiveAutocomplete();
|
||||
if (ac) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.cancel();
|
||||
ac.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
autoCompleteMixinSelectNextAutocomplete (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
const ac = this._getActiveAutocomplete();
|
||||
if (ac) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.selectNext();
|
||||
ac.selectNext();
|
||||
}
|
||||
},
|
||||
|
||||
autoCompleteMixinSelectPreviousAutocomplete (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
const ac = this._getActiveAutocomplete();
|
||||
if (ac) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.selectPrevious();
|
||||
ac.selectPrevious();
|
||||
}
|
||||
},
|
||||
|
||||
autoCompleteMixinSelectAutocomplete (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
if (this.$refs.autocomplete.selected !== null) {
|
||||
const ac = this._getActiveAutocomplete();
|
||||
if (ac) {
|
||||
if (ac.selected !== null) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.makeSelection();
|
||||
ac.makeSelection();
|
||||
} else {
|
||||
// no autocomplete selected, newline instead
|
||||
this.$refs.autocomplete.cancel();
|
||||
ac.cancel();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -153,9 +153,23 @@
|
||||
:placeholder="$t('needsTextPlaceholder')"
|
||||
:maxlength="MAX_MESSAGE_LENGTH"
|
||||
:class="{'has-content': newMessage.trim() !== '', 'disabled': newMessageDisabled}"
|
||||
@keydown="autoCompleteMixinUpdateCarretPosition"
|
||||
@keyup.ctrl.enter="sendPrivateMessage()"
|
||||
@keydown.tab="autoCompleteMixinHandleTab($event)"
|
||||
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
|
||||
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
|
||||
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
|
||||
@keydown.esc="autoCompleteMixinHandleEscape($event)"
|
||||
>
|
||||
</textarea>
|
||||
<emoji-auto-complete
|
||||
ref="emojiAutocomplete"
|
||||
:text="newMessage"
|
||||
:textbox="textbox"
|
||||
:coords="mixinData.autoComplete.coords"
|
||||
:caret-position="mixinData.autoComplete.caretPosition"
|
||||
@select="selectedAutocomplete"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="sub-new-message-row d-flex"
|
||||
@@ -540,6 +554,7 @@ h3 {
|
||||
}
|
||||
|
||||
.new-message-row {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-left: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
@@ -676,6 +691,8 @@ import PmNewMessageStarted from './pm-new-message-started.vue';
|
||||
import StartNewConversationInputHeader from './start-new-conversation-input-header.vue';
|
||||
import positiveIcon from '@/assets/svg/positive.svg?raw';
|
||||
import NotificationMixins from '@/mixins/notifications';
|
||||
import emojiAutoComplete from '@/components/chat/emojiAutoComplete';
|
||||
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
|
||||
|
||||
// extract to a shared path
|
||||
const CONVERSATIONS_PER_PAGE = 10;
|
||||
@@ -700,13 +717,14 @@ export default defineComponent({
|
||||
toggleSwitch,
|
||||
userLink,
|
||||
faceAvatar,
|
||||
emojiAutoComplete,
|
||||
},
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(new Date(value)).fromNow();
|
||||
},
|
||||
},
|
||||
mixins: [styleHelper, NotificationMixins],
|
||||
mixins: [styleHelper, NotificationMixins, autoCompleteHelperMixin],
|
||||
beforeRouteEnter (to, from, next) {
|
||||
next(vm => {
|
||||
const data = vm.$store.state.privateMessageOptions;
|
||||
@@ -751,6 +769,7 @@ export default defineComponent({
|
||||
/** @type {Record<string, PrivateMessages.PrivateMessageEntry[]>} */
|
||||
messagesByConversation: {}, // cache {uuid: []}
|
||||
|
||||
textbox: null,
|
||||
newMessage: '',
|
||||
messages: [],
|
||||
messagesLoading: false,
|
||||
@@ -963,6 +982,15 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
shouldShowInputPanel (val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
this.textbox = this.$refs.textarea;
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('messages'),
|
||||
@@ -1224,6 +1252,13 @@ export default defineComponent({
|
||||
triggerStartNewConversationState () {
|
||||
this.showStartNewConversationInput = true;
|
||||
},
|
||||
selectedAutocomplete (newText, newCaret) {
|
||||
this.newMessage = newText;
|
||||
this.$nextTick(() => {
|
||||
this.textbox.setSelectionRange(newCaret, newCaret);
|
||||
this.textbox.focus();
|
||||
});
|
||||
},
|
||||
async startConversationByUsername (targetUserName) {
|
||||
// check if the target user exists in current conversations, select that conversation
|
||||
/** @type {PrivateMessages.ConversationSummaryMessageEntry} */
|
||||
|
||||
@@ -295,6 +295,10 @@ export default {
|
||||
appState = JSON.parse(appState);
|
||||
if (appState.paymentCompleted) {
|
||||
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
|
||||
if (appState.paymentType === 'groupPlan') {
|
||||
this.$store.state.upgradingGroup = {};
|
||||
this.$store.dispatch('guilds:getGroupPlans', true);
|
||||
}
|
||||
this.$root.$emit('habitica:payment-success', appState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,5 +428,17 @@
|
||||
"interestedLearningMore": "Interested in Learning More?",
|
||||
"checkGroupPlanFAQ": "Check out the <a href='/static/faq#what-is-group-plan'>Group Plans FAQ</a> to learn how to get the most out of your shared task experience.",
|
||||
"groupPlanBillingFYI": "Group Plan subscriptions automatically renew unless you cancel at least 24 hours before the end of your current period. You can cancel from the Group Billing tab of your Group Plan. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you'll see an additional prorated charge for their benefits at your next billing cycle.",
|
||||
"groupPlanBillingFYIShort": "Group Plan subscriptions automatically renew unless you cancel at least 24 hours before the end of your current period. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you'll see an additional prorated charge for their benefits at your next billing cycle."
|
||||
"groupPlanBillingFYIShort": "Group Plan subscriptions automatically renew unless you cancel at least 24 hours before the end of your current period. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you'll see an additional prorated charge for their benefits at your next billing cycle.",
|
||||
"chooseAnOption": "Choose an Option",
|
||||
"upgradeExistingGroup": "Upgrade an Existing Group",
|
||||
"createNewGroup": "Create a New Group",
|
||||
"yourParty": "Your Party",
|
||||
"previouslyUpgradedGroup": "Previously upgraded Group",
|
||||
"inviteOthersForAdditional": "Invite others to your Group for an additional",
|
||||
"perMember": "per member",
|
||||
"additionalMembersProrated": "Additional members invited during the month will be added to the next billing cycle's total as a pro-rated charge.",
|
||||
"oneMember": "1 member",
|
||||
"membersCount": "<%= count %> members",
|
||||
"pendingCount": "(<%= count %> pending)",
|
||||
"upgradeCancelsPendingInvites": "Upgrading your Party will cancel all pending invites"
|
||||
}
|
||||
|
||||
@@ -334,9 +334,13 @@ api.getGroups = {
|
||||
throw new BadRequest(apiError('guildsOnlyPaginate'));
|
||||
}
|
||||
|
||||
const groupFields = basicGroupFields.concat(' description memberCount balance leaderOnly');
|
||||
let groupFields = basicGroupFields.concat(' description memberCount balance leaderOnly');
|
||||
const sort = '-memberCount';
|
||||
|
||||
if (req.query.includeExpiredPlans === 'true') {
|
||||
groupFields = groupFields.concat(' purchased');
|
||||
}
|
||||
|
||||
const filters = {};
|
||||
if (req.query.categories) {
|
||||
const categorySlugs = req.query.categories.split(',');
|
||||
@@ -371,6 +375,10 @@ api.getGroups = {
|
||||
filters.$or.push({ description: searchQuery });
|
||||
}
|
||||
|
||||
if (req.query.includeExpiredPlans === 'true') {
|
||||
filters.includeExpiredPlans = true;
|
||||
}
|
||||
|
||||
const results = await Group.getGroups({
|
||||
user,
|
||||
types,
|
||||
|
||||
@@ -88,7 +88,7 @@ function toSourceMapRegex (token) {
|
||||
let regexStr = '';
|
||||
|
||||
if (type === 'code_block') {
|
||||
regexStr = withOptionalIndentation(contentRegex);
|
||||
regexStr = withOptionalIndentation(contentRegex.replace(/\n$/, ''));
|
||||
} else if (type === 'fence') {
|
||||
regexStr = `\\s*${markup}.*\n${withOptionalIndentation(contentRegex)}\\s*${markup}`;
|
||||
} else if (type === 'code_inline') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mapInboxMessage, inboxModel } from '../../models/message';
|
||||
import { getUserInfo, sendTxn as sendTxnEmail } from '../email'; // eslint-disable-line import/no-cycle
|
||||
import { sendNotification as sendPushNotification } from '../pushNotifications';
|
||||
import { sendNotification as sendPushNotification } from '../pushNotifications'; // eslint-disable-line import/no-cycle
|
||||
|
||||
export async function sentMessage (sender, receiver, message, translate) {
|
||||
const fakeSending = sender.flags.chatShadowMuted;
|
||||
|
||||
@@ -153,6 +153,10 @@ async function prepareSubscriptionValues (data) {
|
||||
groupId = group._id;
|
||||
recipient.purchased.plan.quantity = data.sub.quantity;
|
||||
|
||||
if (group.type === 'party') {
|
||||
await group.removeGroupInvitations();
|
||||
}
|
||||
|
||||
await addSubscriptionToGroupUsers(group);
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,16 @@ schema.plugin(baseModel, {
|
||||
noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'bannedWordsAllowed', 'challengeCount', 'tasksOrder', 'purchased', 'managers'],
|
||||
private: ['purchased.plan'],
|
||||
toJSONTransform (plainObj, originalDoc) {
|
||||
if (plainObj.purchased) plainObj.purchased.active = originalDoc.hasActiveGroupPlan();
|
||||
if (plainObj.purchased) {
|
||||
plainObj.purchased.active = originalDoc.hasActiveGroupPlan();
|
||||
const plan = originalDoc.purchased && originalDoc.purchased.plan;
|
||||
if (plan && plan.customerId) {
|
||||
plainObj.purchased.wasUpgraded = true;
|
||||
if (plan.dateTerminated) {
|
||||
plainObj.purchased.dateTerminated = plan.dateTerminated;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -309,13 +318,16 @@ schema.statics.getGroups = async function getGroups (options = {}) {
|
||||
privacy: 'private',
|
||||
_id: { $in: user.guilds },
|
||||
'purchased.plan.customerId': { $exists: true },
|
||||
$or: [
|
||||
};
|
||||
if (!filters.includeExpiredPlans) {
|
||||
query.$or = [
|
||||
{ 'purchased.plan.dateTerminated': null },
|
||||
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||
{ 'purchased.plan.dateTerminated': { $gt: new Date() } },
|
||||
],
|
||||
};
|
||||
_.assign(query, filters);
|
||||
];
|
||||
}
|
||||
const filtersWithoutCustom = _.omit(filters, ['includeExpiredPlans']);
|
||||
_.assign(query, filtersWithoutCustom);
|
||||
const privateGuildsQuery = this.find(query).select(groupFields);
|
||||
if (populateLeader === true) privateGuildsQuery.populate('leader', nameFields);
|
||||
privateGuildsQuery.sort(sort);
|
||||
|
||||
Reference in New Issue
Block a user