Compare commits

..

1 Commits

Author SHA1 Message Date
CuriousMagpie b8a2f0b8ee update privacy policy 2024-11-26 13:21:31 -06:00
412 changed files with 7758 additions and 13452 deletions
-9
View File
@@ -7,14 +7,5 @@ module.exports = {
rules: {
'prefer-regex-literals': 'warn',
'import/no-extraneous-dependencies': 'off',
'require-await': 'error',
},
overrides: [
{
files: ['migrations/**', 'gulp/**'], // Or *.test.js
rules: {
'require-await': 'off',
},
},
],
};
+1 -8
View File
@@ -1,13 +1,6 @@
name: Test
on:
push:
branches-ignore:
- 'phillip/**'
- 'sabrecat/**'
- 'kalista/**'
- 'natalie/**'
pull_request:
on: [push, pull_request]
permissions:
contents: read
+1 -1
View File
@@ -47,5 +47,5 @@ webpack.webstorm.config
# mongodb replica set for local dev
mongodb-*.tgz
/mongodb-data*
/mongodb-data
/.nyc_output
+1 -1
View File
@@ -2,4 +2,4 @@
This webpage includes the documentation for version 3 of the [Habitica](https://habitica.com) API.
If you're developing a 3rd party tool that uses the Habitica API, read the [API Usage Guidelines](https://github.com/HabitRPG/habitica/wiki/API-Usage-Guidelines), which describe how to be a responsible user of our server resources!
If you're developing a 3rd party tool that uses the Habitica API you should read the [Guidance for Comrades](https://habitica.fandom.com/wiki/Guidance_for_Comrades) and in particular the section called [Rules for Third-Party Tools](https://habitica.fandom.com/wiki/Guidance_for_Comrades#Rules_for_Third-Party_Tools) which includes suggestions on how to best use the API and the rules to follow when interacting with it.
+1 -2
View File
@@ -93,6 +93,5 @@
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
"TIME_TRAVEL_ENABLED": "false",
"DEBUG_ENABLED": "false",
"CONTENT_SWITCHOVER_TIME_OFFSET": 8,
"SLOW_REQUEST_THRESHOLD": 1000
"CONTENT_SWITCHOVER_TIME_OFFSET": 8
}
-9
View File
@@ -64,15 +64,6 @@ function filterFile (file) {
if (file.relative.indexOf('icon_background') === 0) {
return false;
}
if (file.relative.indexOf('notif_') === 0) {
return false;
}
if (file.relative.indexOf('quest_') === 0) {
return false;
}
if (file.relative.indexOf('inventory_quest_') === 0) {
return false;
}
return true;
}
+1 -1
View File
@@ -26,7 +26,7 @@ async function updateUser (user) {
[{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
);
return User.updateOne({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
return User.update({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
}
export default async function processUsers () {
+2 -2
View File
@@ -27,13 +27,13 @@ async function updateUser (user) {
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.updateOne({ _id: user._id }, { $set: set }).exec();
return User.update({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.local.username': 'ExampleHabitican',
'auth.local.lowerCaseUsername': 'olson1',
};
const fields = {
+1 -1
View File
@@ -57,7 +57,7 @@ async function updateUser (user) {
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.local.username': 'ExampleHabitican',
'auth.local.username': 'SabreTest',
};
const fields = {
-125
View File
@@ -1,125 +0,0 @@
/* eslint-disable no-console */
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20231228_nye';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const updateOp = {
$set: { migration: MIGRATION_NAME },
$push: { },
};
const data = {
title: 'Happy New Year!',
destination: '/inventory/equipment',
};
if (typeof user.items.gear.owned.head_special_nye2023 !== 'undefined') {
updateOp.$inc = {
'items.food.Candy_Skeleton': 1,
'items.food.Candy_Base': 1,
'items.food.Candy_CottonCandyBlue': 1,
'items.food.Candy_CottonCandyPink': 1,
'items.food.Candy_Shade': 1,
'items.food.Candy_White': 1,
'items.food.Candy_Golden': 1,
'items.food.Candy_Zombie': 1,
'items.food.Candy_Desert': 1,
'items.food.Candy_Red': 1,
};
data.icon = 'notif_candy_nye';
data.text = 'Youve received an assortment of candy to celebrate with your Pets!';
data.destination = '/inventory/stable';
} else if (typeof user.items.gear.owned.head_special_nye2022 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2023'] = true;
data.icon = 'notif_2023hat_nye';
data.text = 'Take on your resolutions with style in this Ludicrous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2022'] = true;
data.icon = 'notif_2022hat_nye';
data.text = 'Take on your resolutions with style in this Fabulous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2021'] = true;
data.icon = 'notif_2021hat_nye';
data.text = 'Take on your resolutions with style in this Preposterous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2020'] = true;
data.icon = 'notif_2020hat_nye';
data.text = 'Take on your resolutions with style in this Extravagant Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2019'] = true;
data.icon = 'notif_2019hat_nye';
data.text = 'Take on your resolutions with style in this Outrageous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2018'] = true;
data.icon = 'notif_2018hat_nye';
data.text = 'Take on your resolutions with style in this Outlandish Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2017'] = true;
data.icon = 'notif_2017hat_nye';
data.text = 'Take on your resolutions with style in this Fanciful Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2016'] = true;
data.icon = 'notif_2016hat_nye';
data.text = 'Take on your resolutions with style in this Whimsical Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2015'] = true;
data.icon = 'notif_2015hat_nye';
data.text = 'Take on your resolutions with style in this Ridiculous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2014'] = true;
data.icon = 'notif_2014hat_nye';
data.text = 'Take on your resolutions with style in this Silly Party Hat!';
} else {
updateOp.$set['items.gear.owned.head_special_nye'] = true;
data.icon = 'notif_2013hat_nye';
data.text = 'Take on your resolutions with style in this Absurd Party Hat!';
}
updateOp.$push.notifications = {
type: 'ITEM_RECEIVED',
data,
seen: false,
};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.updateOne({ _id: user._id }, updateOp).exec();
}
export default async function processUsers () {
const query = {
'auth.timestamps.loggedin': { $gt: new Date('2023-12-01') },
migration: { $ne: MIGRATION_NAME },
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({ _id: 1 })
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
}
+81 -121
View File
@@ -1,12 +1,12 @@
{
"name": "habitica",
"version": "5.34.2",
"version": "5.31.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
"version": "5.34.2",
"version": "5.31.1",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",
@@ -22,7 +22,7 @@
"apple-auth": "^1.0.9",
"babel-preset-env": "^1.7.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"body-parser": "^1.20.2",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
@@ -33,7 +33,7 @@
"eslint": "^8.55.0",
"eslint-config-habitrpg": "^6.2.3",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.21.1",
"express": "^4.19.2",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
"firebase-admin": "^12.1.1",
@@ -56,7 +56,7 @@
"method-override": "^3.0.0",
"moment": "^2.29.4",
"moment-recur": "^1.0.7",
"mongoose": "^7.8.3",
"mongoose": "^7.6.3",
"morgan": "^1.10.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
@@ -3044,9 +3044,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
"integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==",
"optional": true,
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -6244,9 +6244,9 @@
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -6256,7 +6256,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.13.0",
"qs": "6.11.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -6291,11 +6291,11 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.6"
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
@@ -6577,15 +6577,14 @@
}
},
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
"integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
"get-intrinsic": "^1.2.3",
"set-function-length": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
@@ -7370,9 +7369,9 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
@@ -8377,9 +8376,9 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
@@ -8498,17 +8497,6 @@
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
@@ -9992,36 +9980,36 @@
}
},
"node_modules/express": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.7.1",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.3.1",
"finalhandler": "1.2.0",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.3",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.10",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"qs": "6.11.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.19.0",
"serve-static": "1.16.2",
"send": "0.18.0",
"serve-static": "1.15.0",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -10074,11 +10062,11 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/express/node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.6"
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
@@ -10543,12 +10531,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -13360,27 +13348,10 @@
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
"integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA=="
},
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/ip-address/node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"node_modules/ip-address/node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
@@ -14990,12 +14961,9 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"node_modules/merge-stream": {
"version": "2.0.0",
@@ -15596,13 +15564,13 @@
}
},
"node_modules/mongoose": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.3.tgz",
"integrity": "sha512-eFnbkKgyVrICoHB6tVJ4uLanS7d5AIo/xHkEbQeOv6g2sD7gh/1biRwvFifsmbtkIddQVNr3ROqHik6gkknN3g==",
"version": "7.6.8",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.8.tgz",
"integrity": "sha512-q9zAySH+UtOK5yonWyNcLfq3PxrY6s4gdta4qNGKNOE2yTVoY9FP4hQtvWYnv4rkdk7T8QmQMC7bbhJjDxIunw==",
"dependencies": {
"bson": "^5.5.0",
"kareem": "2.5.1",
"mongodb": "5.9.2",
"mongodb": "5.9.1",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -15625,9 +15593,9 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz",
"integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==",
"version": "5.9.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.1.tgz",
"integrity": "sha512-NBGA8AfJxGPeB12F73xXwozt8ZpeIPmCUeWRwl9xejozTXFes/3zaep9zhzs1B/nKKsw4P3I4iPfXl3K7s6g+Q==",
"dependencies": {
"bson": "^5.5.0",
"mongodb-connection-string-url": "^2.6.0",
@@ -17624,9 +17592,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/path-type": {
"version": "1.1.0",
@@ -19369,9 +19337,9 @@
}
},
"node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -19404,14 +19372,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -19426,14 +19386,14 @@
}
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"dependencies": {
"encodeurl": "~2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.19.0"
"send": "0.18.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -19559,11 +19519,11 @@
}
},
"node_modules/side-channel": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
"dependencies": {
"call-bind": "^1.0.7",
"call-bind": "^1.0.6",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
@@ -19901,15 +19861,15 @@
}
},
"node_modules/socks": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
"integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
"dependencies": {
"ip-address": "^9.0.5",
"ip": "^2.0.0",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"node": ">= 10.13.0",
"npm": ">= 3.0.0"
}
},
+4 -5
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.34.2",
"version": "5.31.1",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -17,7 +17,7 @@
"apple-auth": "^1.0.9",
"babel-preset-env": "^1.7.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"body-parser": "^1.20.2",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
@@ -28,7 +28,7 @@
"eslint": "^8.55.0",
"eslint-config-habitrpg": "^6.2.3",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.21.1",
"express": "^4.19.2",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
"firebase-admin": "^12.1.1",
@@ -51,7 +51,7 @@
"method-override": "^3.0.0",
"moment": "^2.29.4",
"moment-recur": "^1.0.7",
"mongoose": "^7.8.3",
"mongoose": "^7.6.3",
"morgan": "^1.10.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
@@ -110,7 +110,6 @@
"start:simple": "node ./website/server/index.js",
"debug": "gulp nodemon --inspect",
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc",
"heroku-postbuild": ".heroku/report_deploy.sh"
@@ -59,7 +59,7 @@ describe('POST /debug/jump-time', () => {
expect(resultDate.getDate()).to.eql(today.getDate());
expect(resultDate.getMonth()).to.eql(today.getMonth());
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 365 })).time);
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time);
expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1);
});
@@ -60,12 +60,12 @@ describe('PUT /heroes/:heroId', () => {
expect(heroRes.profile).to.have.all.keys(['name']);
// test response values
expect(heroRes.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(heroRes.contributor.level).to.equal(1);
expect(heroRes.purchased.ads).to.equal(true);
// test hero values
await hero.sync();
expect(hero.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(hero.contributor.level).to.equal(1);
expect(hero.purchased.ads).to.equal(true);
expect(hero.auth.blocked).to.equal(prevBlockState);
@@ -136,12 +136,12 @@ describe('PUT /heroes/:heroId', () => {
expect(heroRes.profile).to.have.all.keys(['name']);
// test response values
expect(heroRes.balance).to.equal(15); // 0+15 for sixth contrib level
expect(heroRes.balance).to.equal(1); // 0+1 for sixth contrib level
expect(heroRes.contributor.level).to.equal(6);
expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5);
// test hero values
await hero.sync();
expect(hero.balance).to.equal(15); // 0+15 for sixth contrib level
expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level
expect(hero.contributor.level).to.equal(6);
expect(hero.items.pets['Dragon-Hydra']).to.equal(5);
});
@@ -1,56 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import common from '../../../../../website/common';
describe('GET /members/username/:username', () => {
let user;
before(async () => {
user = await generateUser();
});
it('validates req.params.username', async () => {
await expect(user.get('/members/username/')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns a member\'s public data only', async () => {
// make sure user has all the fields that can be returned by the getMember call
const member = await generateUser({
contributor: { level: 1 },
backer: { tier: 3 },
preferences: {
costume: false,
background: 'volcano',
},
secret: {
text: 'Clark Kent',
},
});
const memberRes = await user.get(`/members/username/${member.auth.local.username}`);
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
]);
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
].sort());
expect(memberRes.stats.maxMP).to.exist;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
expect(memberRes.inbox.optOut).to.exist;
expect(memberRes.inbox.canReceive).to.exist;
expect(memberRes.inbox.messages).to.not.exist;
expect(memberRes.secret).to.not.exist;
expect(memberRes.blocks).to.not.exist;
});
});
@@ -1,104 +0,0 @@
import find from 'lodash/find';
import {
generateUser,
translate as t,
} from '../../../helpers/api-integration/v4';
/**
* Checks the messages array if the uniqueMessageId has the like flag
* @param {InboxMessage[]} messages
* @param {String} uniqueMessageId
* @param {String} userId
* @param {Boolean} likeStatus
*/
function expectMessagesLikeStatus (messages, uniqueMessageId, userId, likeStatus) {
const messageToCheck = find(messages, { uniqueMessageId });
expect(messageToCheck.likes[userId]).to.equal(likeStatus);
}
// eslint-disable-next-line mocha/no-exclusive-tests
describe('POST /inbox/like-private-message/:messageId', () => {
let userToSendMessage;
const getLikeUrl = messageId => `/inbox/like-private-message/${messageId}`;
before(async () => {
userToSendMessage = await generateUser();
});
it('returns an error when private message is not found', async () => {
await expect(userToSendMessage.post(getLikeUrl('some-unknown-id')))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('likes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const senderMessages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(senderMessages, uniqueMessageId, receiver._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, receiver._id, true);
});
it('allows a user to like their own private message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await userToSendMessage.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[userToSendMessage._id]).to.equal(true);
const messages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(messages, uniqueMessageId, userToSendMessage._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, userToSendMessage._id, true);
});
it('unlikes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const unlikeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(unlikeResult.likes[receiver._id]).to.equal(false);
const messages = await userToSendMessage.get('/inbox/messages');
const messageToCheck = find(messages, { id: sentMessageResult.message.id });
expect(messageToCheck.likes[receiver._id]).to.equal(false);
});
});
+1 -1
View File
@@ -10,7 +10,7 @@ describe('events', () => {
});
it('returns empty array when no events are active', () => {
clock = sinon.useFakeTimers(new Date('2024-01-11'));
clock = sinon.useFakeTimers(new Date('2024-01-08'));
const events = getRepeatingEvents();
expect(events).to.be.empty;
});
+2 -30
View File
@@ -144,12 +144,6 @@ describe('Content Schedule', () => {
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
});
it('sets the end date in new year for a winter gala', () => {
const date = new Date('2025-01-04');
const matchers = getAllScheduleMatchingGroups(date);
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
});
it('uses correct date for first hours of the month', () => {
// if the date is checked before CONTENT_SWITCHOVER_TIME_OFFSET,
// it should be considered the previous month
@@ -272,21 +266,6 @@ describe('Content Schedule', () => {
expect(matcher.match('backgroundkey072024')).to.be.true;
});
it('allows background matching the month for new backgrounds from multiple years', () => {
const date = new Date('2026-07-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey072024')).to.be.true;
expect(matcher.match('backgroundkey072025')).to.be.true;
expect(matcher.match('backgroundkey072026')).to.be.true;
});
it('allows background matching the previous month in the first week for new backgrounds', () => {
const date = new Date('2024-09-02');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey082024')).to.be.true;
expect(matcher.match('backgroundkey092024')).to.be.false;
});
it('disallows background in the future', () => {
const date = new Date('2024-07-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
@@ -306,26 +285,19 @@ describe('Content Schedule', () => {
expect(matcher.match('backgroundkey022021')).to.be.true;
});
it('allows even yeared backgrounds in first half of year', () => {
it('allows background even yeared backgrounds in first half of year', () => {
const date = new Date('2025-02-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey022024')).to.be.true;
expect(matcher.match('backgroundkey082022')).to.be.true;
});
it('allows odd yeared backgrounds in second half of year', () => {
it('allows background odd yeared backgrounds in second half of year', () => {
const date = new Date('2024-08-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey022023')).to.be.true;
expect(matcher.match('backgroundkey082021')).to.be.true;
});
it('allows odd yeared backgrounds in beginning of january', () => {
const date = new Date('2025-01-06');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey122024'), 'backgroundkey122024').to.be.true;
expect(matcher.match('backgroundkey062023'), 'backgroundkey062022').to.be.true;
});
});
describe('timeTravelers matcher', () => {
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = {
extends: [
'habitrpg/lib/vue',
],
ignorePatterns: ['dist/', 'node_modules/', '*.d.ts'],
ignorePatterns: ['dist/', 'node_modules/'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+76 -210
View File
@@ -3934,9 +3934,9 @@
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -3946,7 +3946,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.13.0",
"qs": "6.11.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -4140,33 +4140,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"get-intrinsic": "^1.2.6"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4643,9 +4616,9 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
@@ -5449,19 +5422,6 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/dunder-proto": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
"integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -5504,9 +5464,9 @@
}
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
@@ -5628,9 +5588,12 @@
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
@@ -5648,17 +5611,6 @@
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
},
"node_modules/es-object-atoms": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
@@ -6743,36 +6695,36 @@
}
},
"node_modules/express": {
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.7.1",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.3.1",
"finalhandler": "1.2.0",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.3",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.12",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"qs": "6.11.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.19.0",
"serve-static": "1.16.2",
"send": "0.18.0",
"serve-static": "1.15.0",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -6781,10 +6733,6 @@
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/array-flatten": {
@@ -6929,12 +6877,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -7177,20 +7125,15 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"dunder-proto": "^1.0.0",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.0.0",
"function-bind": "^1.1.2",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.0.0"
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
@@ -7310,11 +7253,11 @@
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"engines": {
"node": ">= 0.4"
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7410,9 +7353,9 @@
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
@@ -7440,9 +7383,9 @@
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -8976,14 +8919,6 @@
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/math-intrinsics": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@@ -9023,12 +8958,9 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"node_modules/merge-source-map": {
"version": "1.1.0",
@@ -9930,12 +9862,9 @@
}
},
"node_modules/object-inspect": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
"engines": {
"node": ">= 0.4"
},
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -10381,9 +10310,9 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/path-type": {
"version": "4.0.0",
@@ -11200,11 +11129,11 @@
}
},
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.6"
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
@@ -11807,9 +11736,9 @@
"dev": true
},
"node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -11842,14 +11771,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -11934,14 +11855,14 @@
}
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"dependencies": {
"encodeurl": "~2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.19.0"
"send": "0.18.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -12030,68 +11951,13 @@
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
File diff suppressed because it is too large Load Diff
+2 -11
View File
@@ -101,7 +101,8 @@
.btn-secondary,
.dropdown > .btn-secondary.dropdown-toggle:not(.btn-success),
.show > .btn-secondary.dropdown-toggle:not(.btn-success) {
.show > .btn-secondary.dropdown-toggle:not(.btn-success)
{
background: $white;
border: 2px solid transparent;
color: $gray-50;
@@ -297,16 +298,6 @@
box-shadow: none;
}
.btn-flat,
.dropdown > .btn-flat.dropdown-toggle:not(.btn-success),
.show > .btn-flat.dropdown-toggle:not(.btn-success) {
&.with-icon {
.svg-icon.color {
color: var(--icon-color);
}
}
}
.btn-cancel {
color: $blue-10;
}
+2 -9
View File
@@ -38,12 +38,7 @@
border-radius: 2px;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
padding: 0;
}
.no-min-width {
.dropdown-menu {
min-width: 0 !important;
}
}
// shared dropdown-item styles
@@ -59,8 +54,6 @@
color: $gray-50 !important;
cursor: pointer;
--dropdown-item-hover-icon-color: #{$gray-200};
&:focus {
outline: none;
background-color: inherit;
@@ -95,7 +88,7 @@
&:not(:hover) {
.with-icon .svg-icon {
color: var(dropdown-item-hover-icon-color);
color: $gray-200;
}
}
}
@@ -158,7 +151,7 @@
// selectList.vue items sizing
.selectListItem .dropdown-item {
padding: 0.25rem 1rem 0.25rem 0.75rem;
padding: 0.25rem 0.75rem;
height: 32px;
&:active, &:hover, &:focus, &.active {
@@ -1,3 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M4,0C1.79,0,0,1.79,0,4v16c0,2.21,1.79,4,4,4h16c2.21,0,4-1.79,4-4V4c0-2.21-1.79-4-4-4H4ZM12,11.57c-.72-1.49-2.7-4.26-4.53-5.63-1.32-.99-3.47-1.75-3.47.68,0,.49.28,4.08.44,4.66.57,2.03,2.65,2.55,4.5,2.23-3.24.55-4.06,2.36-2.28,4.17,3.38,3.44,4.85-.86,5.23-1.97h0s0,0,0,0c.07-.2.1-.29.1-.21,0-.08.03.01.1.22h0c.38,1.1,1.85,5.41,5.23,1.97,1.78-1.81.95-3.63-2.28-4.17,1.85.31,3.93-.2,4.5-2.23.16-.58.44-4.18.44-4.66,0-2.43-2.14-1.67-3.47-.68-1.83,1.37-3.81,4.14-4.53,5.63Z" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 572 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M20,0H4A4,4,0,0,0,0,4V20a4,4,0,0,0,4,4H20a4,4,0,0,0,4-4V4A4,4,0,0,0,20,0ZM18.36,8.74c0,.14,0,.29,0,.43A9.34,9.34,0,0,1,4,17a6.85,6.85,0,0,0,.79,0,6.57,6.57,0,0,0,4.07-1.4A3.29,3.29,0,0,1,5.8,13.39a4.1,4.1,0,0,0,.62,0,3.49,3.49,0,0,0,.86-.11,3.28,3.28,0,0,1-2.63-3.22v0a3.35,3.35,0,0,0,1.48.42A3.29,3.29,0,0,1,4.67,7.76,3.22,3.22,0,0,1,5.12,6.1a9.3,9.3,0,0,0,6.76,3.43,3.67,3.67,0,0,1-.08-.75,3.28,3.28,0,0,1,5.67-2.24,6.54,6.54,0,0,0,2.08-.79,3.22,3.22,0,0,1-1.44,1.8A6.67,6.67,0,0,0,20,7.05,7.31,7.31,0,0,1,18.36,8.74Z" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 622 B

+8 -2
View File
@@ -25,9 +25,9 @@
<router-link to="/">
Homepage
</router-link>or
<a href="mailto:admin@habitica.com">
<router-link :to="contactUsLink">
Contact Us
</a>about the issue.
</router-link>about the issue.
</p>
</div>
</div>
@@ -40,6 +40,12 @@ import { mapState } from '@/libs/store';
export default {
computed: {
...mapState(['isUserLoggedIn']),
contactUsLink () {
if (this.isUserLoggedIn) {
return { name: 'guild', params: { groupId: 'a29da26b-37de-4a71-b0c6-48e72a900dac' } };
}
return { name: 'contact' };
},
retiredChatPage () {
return this.$route.fullPath.indexOf('/groups') !== -1;
},
@@ -8,7 +8,7 @@
<div class="modal-body">
<div class="row">
<div class="col-6 offset-3">
<Sprite image-name="shop_armoire" />
<div class="shop_armoire"></div>
<p>{{ $t('armoireLastItem') }}</p>
<p>{{ $t('armoireNotesEmpty') }}</p>
</div>
@@ -34,12 +34,7 @@
</style>
<script>
import Sprite from '@/components/ui/sprite';
export default {
components: {
Sprite,
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'armoire-empty');
@@ -95,11 +95,7 @@
@click="clickDisableClasses(); close();"
>{{ $t('optOutOfClasses') }}</span>
</div>
<div
v-once
class="opt-out-description"
v-html="$t('optOutOfClassesText')"
></div>
<span class="opt-out-description">{{ $t('optOutOfClassesText') }}</span>
</div>
</div>
</div>
@@ -48,7 +48,7 @@
></span>
</div>
<Sprite :image-name="questClass" />
<div :class="questClass"></div>
</section>
<!-- @TODO: Keep this? .checkboxinput(type='checkbox', v-model=
'user.preferences.suppressModals.levelUp', @change='changeLevelupSuppress()')
@@ -150,12 +150,15 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
section.greyed {
padding-bottom: 17px
}
.scroll {
margin: -11px auto 0;
}
}
</style>
<script>
import Avatar from '../avatar';
import Sprite from '@/components/ui/sprite';
import { mapState } from '@/libs/store';
import starGroup from '@/assets/svg/star-group.svg';
import sparkles from '@/assets/svg/sparkles-left.svg';
@@ -170,7 +173,6 @@ const levelQuests = {
export default {
components: {
Avatar,
Sprite,
},
data () {
return {
@@ -189,9 +191,7 @@ export default {
return this.user.stats.lvl in levelQuests;
},
questClass () {
const questKey = levelQuests[this.user.stats.lvl];
if (questKey) return `inventory_quest_scroll_${questKey}`;
return '';
return `scroll inventory_quest_scroll_${levelQuests[this.user.stats.lvl]}`;
},
},
methods: {
@@ -55,7 +55,7 @@
<p v-html="$t('moreGearAchievements')"></p>
<br>
</div>
<Sprite image-name="shop_armoire" />
<div class="shop_armoire"></div>
<p v-html="$t('armoireUnlocked')"></p>
<br>
<button
@@ -87,13 +87,11 @@
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import { mapState } from '@/libs/store';
import Sprite from '@/components/ui/sprite.vue';
export default {
components: {
achievementFooter,
achievementAvatar,
Sprite,
},
computed: {
...mapState({ user: 'user.data' }),
@@ -27,7 +27,7 @@
{{ item.value }}
</span>
:
{{ itemText(item) }}
{{ item.text || item.key }}
</span>
<div
@@ -78,7 +78,7 @@
{{ item.value }}
</span>
:
{{ itemText(item) }}
{{ item.text || item.key }}
</span>
<div
@@ -270,15 +270,6 @@ export default {
item.value = !item.value;
}
},
itemText (item) {
if (item.key === 'npc') {
return this.$t('npcAchievementName', { key: this.hero.backer && this.hero.backer.npc });
}
if (item.key === 'kickstarter') {
return this.$t('kickstartName', { key: this.hero.backer && this.hero.backer.tier });
}
return item.text || item.key;
},
},
};
</script>
+28 -16
View File
@@ -37,9 +37,9 @@
<h3>{{ $t('footerCompany') }}</h3>
<ul>
<li>
<a href="mailto:admin@habitica.com">
<router-link to="/static/contact">
{{ $t('contactUs') }}
</a>
</router-link>
</li>
<li>
<router-link to="/static/press-kit">
@@ -55,9 +55,9 @@
</li>
<li>
<a
@click="showBailey()"
>
{{ $t('oldNews') }}
href="https://habitica.fandom.com/wiki/Whats_New"
target="_blank"
>{{ $t('oldNews') }}
</a>
</li>
</ul>
@@ -80,7 +80,7 @@
</li>
<li>
<a
href="https://github.com/HabitRPG/habitica/wiki/Contributing-to-Habitica"
href="https://habitica.fandom.com/wiki/Contributing_to_Habitica"
target="_blank"
>{{ $t('companyContribute') }}
</a>
@@ -131,6 +131,13 @@
>{{ $t('requestFeature') }}
</a>
</li>
<li>
<a
href="https://habitica.fandom.com/"
target="_blank"
>{{ $t('wiki') }}
</a>
</li>
</ul>
</div>
<!-- Developers -->
@@ -158,6 +165,13 @@
>{{ $t('guidanceForBlacksmiths') }}
</a>
</li>
<li>
<a
href="https://habitica.fandom.com/wiki/Extensions,_Add-Ons,_and_Customizations"
target="_blank"
>{{ $t('communityExtensions') }}
</a>
</li>
</ul>
</div>
@@ -198,12 +212,12 @@
</a>
<a
class="social-circle"
href="https://bsky.app/profile/habitica.com"
href="https://twitter.com/habitica/"
target="_blank"
>
<div
class="social-icon svg-icon bluesky"
v-html="icons.bluesky"
class="social-icon svg-icon twitter"
v-html="icons.twitter"
></div>
</a>
<a
@@ -511,7 +525,7 @@ footer {
background-color: $gray-500;
color: $gray-50;
padding: 32px 142px 40px;
a, a:not([href]) {
a {
color: $gray-50;
}
a:hover {
@@ -800,7 +814,7 @@ h3 {
}
}
.bluesky svg {
.twitter svg {
background-color: #e1e0e3;
fill: #878190;
height: 24px;
@@ -839,7 +853,7 @@ import Vue from 'vue';
// images
import melior from '@/assets/svg/melior.svg';
import bluesky from '@/assets/svg/bluesky.svg';
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
import instagram from '@/assets/svg/instagram.svg';
import tumblr from '@/assets/svg/tumblr.svg';
@@ -871,7 +885,7 @@ export default {
return {
icons: Object.freeze({
melior,
bluesky,
twitter,
facebook,
instagram,
tumblr,
@@ -989,6 +1003,7 @@ export default {
async bossRage () {
await axios.post('/api/v4/debug/boss-rage');
},
async makeAdmin () {
await axios.post('/api/v4/debug/make-admin');
// @TODO: Notification.text('You are now an admin!
@@ -998,9 +1013,6 @@ export default {
donate () {
this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true });
},
showBailey () {
this.$root.$emit('bv::show::modal', 'new-stuff');
},
},
};
</script>
+7 -77
View File
@@ -3,7 +3,7 @@
v-if="member.preferences"
class="avatar"
:style="{width, height, paddingTop}"
:class="topLevelClassList"
:class="backgroundClass"
@click.prevent="castEnd()"
>
<div
@@ -55,11 +55,7 @@
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
<span :class="[getGearClass('head'), specialMountClass]"></span>
<span :class="[getGearClass('headAccessory'), specialMountClass]"></span>
<span
:class="[
'hair_flower_' + member.preferences.hair.flower, specialMountClass
]"
></span>
<span :class="['hair_flower_' + member.preferences.hair.flower, specialMountClass]"></span>
<span
v-if="!hideGear('shield')"
:class="[getGearClass('shield'), specialMountClass]"
@@ -67,7 +63,6 @@
<span
v-if="!hideGear('weapon')"
:class="[getGearClass('weapon'), specialMountClass]"
class="weapon"
></span>
</template>
<!-- Resting-->
@@ -101,23 +96,15 @@
.avatar {
width: 141px;
height: 147px;
image-rendering: pixelated;
position: relative;
cursor: pointer;
&.centered-avatar {
margin: 0 auto;
}
// resetting the additional padding
margin-bottom: -0.5rem !important;
}
.character-sprites {
width: 90px;
height: 90px;
display: inline-flex;
}
.character-sprites span {
@@ -136,22 +123,6 @@
.invert {
filter: invert(100%);
}
.debug {
border: 1px solid red;
.character-sprites {
border: 1px solid blue;
}
.weapon {
border: 1px solid green;
}
span {
border: 1px solid yellow;
}
}
</style>
<script>
@@ -162,24 +133,12 @@ import foolPet from '../mixins/foolPet';
import ClassBadge from '@/components/members/classBadge';
/**
* TODO replace avatarOnly with multiple options like
* - showMount
* - showPet
* - showBackground
* - showWeapons
*/
export default {
components: {
ClassBadge,
},
mixins: [foolPet],
props: {
debugMode: {
type: Boolean,
default: false,
},
member: {
type: Object,
required: true,
@@ -197,21 +156,14 @@ export default {
},
overrideAvatarGear: {
type: Object,
default (data) {
return data;
},
},
width: {
type: String,
default: '140px',
type: Number,
default: 140,
},
height: {
type: String,
default: undefined,
},
centerAvatar: {
type: Boolean,
default: false,
type: Number,
default: 147,
},
spritesMargin: {
type: String,
@@ -219,16 +171,11 @@ export default {
},
overrideTopPadding: {
type: String,
default: null,
},
showVisualBuffs: {
type: Boolean,
default: true,
},
showWeapon: {
type: Boolean,
default: true,
},
},
computed: {
...mapState({
@@ -257,19 +204,6 @@ export default {
return val;
},
topLevelClassList () {
const classes = [this.backgroundClass];
if (this.debugMode) {
classes.push('debug');
}
if (this.centerAvatar) {
classes.push('centered-avatar');
}
return classes.join(' ');
},
backgroundClass () {
if (this.member) {
const { background } = this.member.preferences;
@@ -356,10 +290,6 @@ export default {
},
hideGear (gearType) {
if (!this.member) return true;
if (!this.showWeapon) {
return true;
}
if (gearType === 'weapon') {
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];
@@ -0,0 +1,352 @@
<template>
<div>
<div
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="hasPermission(user, 'moderator') && msg.flagCount"
class="message-hidden"
>
{{ flagCountDescription }}
</div>
<div class="card-body">
<user-link
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span>
<span
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">({{ msg.client }})</span>
</p>
<div
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
></div>
<hr>
<div
v-if="msg.id"
class="d-flex"
>
<div
class="action d-flex align-items-center"
@click="copyAsTodo(msg)"
>
<div
class="svg-icon"
v-html="icons.copy"
></div>
<div>{{ $t('copyAsTodo') }}</div>
</div>
<div
v-if="(user.flags.communityGuidelinesAccepted && msg.uuid !== 'system')
&& (!isMessageReported || hasPermission(user, 'moderator'))"
class="action d-flex align-items-center"
@click="report(msg)"
>
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
</div>
<div
v-if="msg.uuid === user._id || hasPermission(user, 'moderator')"
class="action d-flex align-items-center"
@click="remove()"
>
<div
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div v-once>
{{ $t('delete') }}
</div>
</div>
<div
v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}"
class="ml-auto d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('liked')"
v-html="icons.liked"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="!msg.likes[user._id]">{{ $t('like') }}</span>
</div>
</div>
</div>
</template>
<style lang="scss">
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
}
.at-text {
color: #6133b4;
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #bda8ff;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.card-body {
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
.time {
font-size: 12px;
color: #878190;
margin-bottom: 0.5rem;
}
.text {
font-size: 14px;
color: #4e4a57;
text-align: initial;
min-height: 0rem;
}
}
.action {
display: inline-block;
color: #878190;
margin-right: 1em;
font-size: 12px;
:hover {
cursor: pointer;
}
.svg-icon {
color: #A5A1AC;
margin-right: .2em;
width: 16px;
}
}
.activeLike {
color: $purple-300;
.svg-icon {
color: $purple-400;
}
}
</style>
<script>
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
import renderWithMentions from '@/libs/renderWithMentions';
import { userStateMixin } from '../../mixins/userState';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import copyIcon from '@/assets/svg/copy.svg';
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
import reportIcon from '@/assets/svg/report.svg';
export default {
components: { userLink },
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
mixins: [userStateMixin],
props: {
msg: {},
groupId: {},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
copy: copyIcon,
report: reportIcon,
delete: deleteIcon,
liked: likedIcon,
}),
reported: false,
};
},
computed: {
isUserMentioned () {
const message = this.msg;
if (message.highlight) return true;
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
flagCountDescription () {
if (!this.msg.flagCount) return '';
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) return 'Message flagged once, not hidden';
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) return 'Message hidden';
return 'Message hidden (shadow-muted)';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
},
mounted () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
this.$emit('chat-card-mounted', this.msg.id);
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatId: message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
copyAsTodo (message) {
this.$root.$emit('habitica::copy-as-todo', message);
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
this.reported = true;
}
this.$root.$off('habitica:report-result');
});
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: this.groupId || 'privateMessage',
});
},
async remove () {
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
const message = this.msg;
this.$emit('message-removed', message);
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);
},
},
};
</script>
@@ -3,6 +3,15 @@
ref="container"
class="container-fluid"
>
<div class="row">
<div class="col-12">
<copy-as-todo-modal
:group-type="groupType"
:group-name="groupName"
:group-id="groupId"
/>
</div>
</div>
<div class="row loadmore">
<div v-if="canLoadMore">
<div class="loadmore-divider"></div>
@@ -24,8 +33,6 @@
<div
v-for="msg in messages.filter(m => chat && canViewFlag(m))"
:key="msg.id"
class="message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div class="d-flex">
<avatar
@@ -38,14 +45,16 @@
:override-top-padding="'14px'"
@click.native="showMemberModal(msg.uuid)"
/>
<message-card
:msg="msg"
:group-id="groupId"
:user-sent-message="user._id === msg.uuid"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@message-card-mounted="itemWasMounted"
/>
<div class="card">
<chat-card
:msg="msg"
:group-id="groupId"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@chat-card-mounted="itemWasMounted"
/>
</div>
<avatar
v-if="user._id === msg.uuid"
:class="{ invisible: avatarUnavailable(msg) }"
@@ -96,6 +105,11 @@
}
}
.avatar-left {
margin-left: -1.5rem;
margin-right: 2rem;
}
.hr {
width: 100%;
height: 20px;
@@ -123,27 +137,11 @@
margin-bottom: .5em;
padding: 0rem;
width: 90%;
&.system-message {
width: 100%;
}
}
.message-scroll .d-flex {
min-width: 1px;
}
.message-row {
margin-left: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
}
}
}
</style>
<script>
@@ -154,13 +152,13 @@ import findIndex from 'lodash/findIndex';
import { userStateMixin } from '../../mixins/userState';
import Avatar from '../avatar';
import MessageCard from '@/components/messages/messageCard.vue';
// TODO merge chatMessages.vue (party message list) with messageList.vue (private message list)
import copyAsTodoModal from './copyAsTodoModal';
import chatCard from './chatCard';
export default {
components: {
MessageCard,
copyAsTodoModal,
chatCard,
Avatar,
},
mixins: [userStateMixin],
@@ -0,0 +1,105 @@
<template>
<b-modal
id="copyAsTodo"
:title="$t('copyMessageAsToDo')"
:hide-footer="true"
size="md"
>
<div class="form-group">
<input
v-model="task.text"
class="form-control"
type="text"
>
</div>
<div class="form-group">
<textarea
v-model="task.notes"
class="form-control"
rows="5"
focus-element="true"
></textarea>
</div>
<hr>
<task
v-if="task._id"
:is-user="isUser"
:task="task"
/>
<div class="modal-footer">
<button
class="btn btn-secondary"
@click="close()"
>
{{ $t('close') }}
</button>
<button
class="btn btn-primary"
@click="saveTodo()"
>
{{ $t('submit') }}
</button>
</div>
</b-modal>
</template>
<script>
import taskDefaults from '@/../../common/script/libs/taskDefaults';
import { mapActions } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import notificationsMixin from '@/mixins/notifications';
import Task from '@/components/tasks/task';
const baseUrl = 'https://habitica.com';
export default {
directives: {
markdown: markdownDirective,
},
components: {
Task,
},
mixins: [notificationsMixin],
props: ['copyingMessage', 'groupType', 'groupName', 'groupId'],
data () {
return {
isUser: true,
task: {},
};
},
mounted () {
this.$root.$on('habitica::copy-as-todo', message => {
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${this.groupPath()})`;
const newTask = {
text: message.text,
type: 'todo',
notes,
};
this.task = taskDefaults(newTask, this.$store.state.user.data);
this.$root.$emit('bv::show::modal', 'copyAsTodo');
});
},
beforeDestroy () {
this.$root.$off('habitica::copy-as-todo');
},
methods: {
...mapActions({
createTask: 'tasks:create',
}),
groupPath () {
if (this.groupType === 'party') {
return `${baseUrl}/party`;
}
return `${baseUrl}/groups/guild/${this.groupId}`;
},
close () {
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
saveTodo () {
this.createTask(this.task);
this.text(this.$t('messageAddedAsToDo'));
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
},
};
</script>
+97 -14
View File
@@ -22,13 +22,13 @@
:placeholder="placeholder"
:class="{'user-entry': newMessage}"
:maxlength="MAX_MESSAGE_LENGTH"
@keydown="autoCompleteMixinUpdateCarretPosition"
@keydown="updateCarretPosition"
@keyup.ctrl.enter="sendMessageShortcut()"
@keydown.tab="autoCompleteMixinHandleTab($event)"
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
@keydown.esc="autoCompleteMixinHandleEscape($event)"
@keydown.tab="handleTab($event)"
@keydown.up="selectPreviousAutocomplete($event)"
@keydown.down="selectNextAutocomplete($event)"
@keypress.enter="selectAutocomplete($event)"
@keydown.esc="handleEscape($event)"
@paste="disableMessageSendShortcut()"
></textarea>
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
@@ -36,8 +36,8 @@
ref="autocomplete"
:text="newMessage"
:textbox="textbox"
:coords="mixinData.autoComplete.coords"
:caret-position="mixinData.autoComplete.caretPosition"
:coords="coords"
:caret-position="caretPosition"
:chat="group.chat"
@select="selectedAutocomplete"
/>
@@ -74,7 +74,7 @@
<slot name="additionRow"></slot>
<div class="row">
<div class="hr col-12"></div>
<chat-messages
<chat-message
:chat.sync="group.chat"
:group-type="group.type"
:group-id="group._id"
@@ -86,15 +86,16 @@
</template>
<script>
import debounce from 'lodash/debounce';
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines';
import chatMessages from '../chat/chatMessages';
import chatMessage from '../chat/chatMessages';
import { mapState } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
export default {
directives: {
@@ -103,18 +104,23 @@ export default {
components: {
autocomplete,
communityGuidelines,
chatMessages,
chatMessage,
},
mixins: [externalLinks, autoCompleteHelperMixin],
mixins: [externalLinks],
props: ['label', 'group', 'placeholder'],
data () {
return {
newMessage: '',
sending: false,
caretPosition: 0,
chat: {
submitDisable: false,
submitTimeout: null,
},
coords: {
TOP: 0,
LEFT: 0,
},
textbox: null,
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
};
@@ -136,6 +142,35 @@ export default {
this.handleExternalLinks();
},
methods: {
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {
this.caretPosition = text.selectionEnd;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, this.caretPosition);
span.textContent = text.value.substr(this.caretPosition) || '.';
div.appendChild(span);
this.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
async sendMessageShortcut () {
// If the user recently pasted in the text field, don't submit
if (!this.chat.submitDisable) {
@@ -186,6 +221,50 @@ export default {
}, 500);
},
handleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
handleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
selectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
selectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
selectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
selectedAutocomplete (newText, newCaret) {
this.newMessage = newText;
// Wait for v-modal to update
@@ -194,6 +273,7 @@ export default {
this.textbox.focus();
});
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
@@ -204,7 +284,10 @@ export default {
beforeRouteUpdate (to, from, next) {
// Reset chat
this.newMessage = '';
this.autoCompleteMixinResetCoordsPosition();
this.coords = {
TOP: 0,
LEFT: 0,
};
next();
},
@@ -225,9 +225,10 @@
</a>
</div>
<div class="quest-icon">
<Sprite
<div
class="quest"
:image-name="`inventory_quest_scroll_${questData.key}`" />
:class="`inventory_quest_scroll_${questData.key}`"
></div>
</div>
</div>
<div
@@ -297,7 +297,7 @@
<div class="topbar-dropdown">
<router-link
v-if="user.permissions.fullAccess ||
user.permissions.userSupport"
user.permissions.userSupport || user.permissions.newsPoster"
class="topbar-dropdown-item dropdown-item"
:to="{name: 'adminPanel'}"
>
@@ -334,6 +334,11 @@
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
target="_blank"
>{{ $t('requestFeature') }}</a>
<a
class="topbar-dropdown-item dropdown-item"
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
target="_blank"
>{{ $t('wiki') }}</a>
</div>
</li>
</b-navbar-nav>
@@ -12,21 +12,20 @@
<strong> {{ notification.data.title }} </strong>
<span> {{ notification.data.text }} </span>
</div>
<Sprite
<div
slot="icon"
class="mt-3"
:image-name="notification.data.icon" />
:class="notification.data.icon"
></div>
</base-notification>
</template>
<script>
import BaseNotification from './base';
import Sprite from '@/components/ui/sprite.vue';
export default {
components: {
BaseNotification,
Sprite,
},
props: {
notification: {
@@ -42,8 +41,7 @@ export default {
},
methods: {
action () {
if (!this.notification || !this.notification.data
|| this.notification.data.destination === this.$route.path) {
if (!this.notification || !this.notification.data) {
return;
}
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {
@@ -10,21 +10,20 @@
slot="content"
v-html="$t('newSubscriberItem')"
></div>
<Sprite
<div
slot="icon"
:image-name="mysteryClass" />
:class="mysteryClass"
></div>
</base-notification>
</template>
<script>
import moment from 'moment';
import BaseNotification from './base';
import Sprite from '@/components/ui/sprite.vue';
export default {
components: {
BaseNotification,
Sprite,
},
props: ['notification', 'canRemove'],
computed: {
@@ -106,7 +106,7 @@
</div>
<div slot="drawer-header">
<div class="drawer-tab-container">
<div class="clearfix mb-2">
<div class="clearfix">
<toggle-switch
class="float-right align-with-tab"
:label="$t(costumeMode ? 'useCostume' : 'autoEquipBattleGear')"
@@ -28,6 +28,7 @@
:name="member.profile.name"
:backer="member.backer"
:contributor="member.contributor"
:smaller-style="true"
/>
<inline-class-badge
v-if="member.stats"
@@ -62,7 +62,7 @@
<script>
import moment from 'moment';
import userLabel from '../../components/userLabel.vue';
import userLabel from '../userLabel';
import dots from '@/assets/svg/dots.svg';
import block from '@/assets/svg/block.svg';
@@ -117,7 +117,7 @@ export default {
</script>
<style lang="scss">
@import '~@/assets/scss/colors';
@import '~@/assets/scss/colors.scss';
.action-padding {
height: 24px !important;
@@ -153,7 +153,7 @@ export default {
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors';
@import '~@/assets/scss/colors.scss';
.conversation {
padding: 1rem 1.5rem;
@@ -1,110 +0,0 @@
<template>
<div
class="d-inline-flex like-button"
@click="like()"
>
<div
v-b-tooltip="{title: likeTooltip(likeCount)}"
class="d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{isLiked: true, currentUserLiked: likedByCurrentUser}"
>
<div
class="svg-icon mr-1"
:title="$t('liked')"
v-html="icons.liked"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-1"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="likeCount === 0">{{ $t('like') }}</span>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.action {
display: inline-block;
margin-right: 1em;
.svg-icon {
color: $gray-100;
width: 16px;
}
&.isLiked {
color: $purple-200;
font-weight: bold;
.svg-icon {
color: $purple-300;
}
}
}
.like-button {
color: $gray-100;
font-size: 12px;
line-height: 16px;
&:hover {
cursor: pointer;
color: $purple-200;
.svg-icon {
color: $purple-300;
}
}
}
</style>
<script>
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
export default {
props: {
likeCount: {
type: Number,
},
likedByCurrentUser: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
liked: likedIcon,
}),
};
},
methods: {
async like () {
this.$emit('toggle-like');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
},
};
</script>
@@ -1,157 +1,73 @@
<template>
<div
class="card"
:class="{
'system-message': isSystemMessage
}"
>
<div class="card-body">
<user-link
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span><span
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4"> ({{ msg.client }})</span>
</p>
<div
v-b-tooltip.hover="messageDateForSystemMessage"
class="message-card"
:class="{
'user-sent-message': userSentMessage,
'user-received-message': !userSentMessage && !isSystemMessage,
'system-message': isSystemMessage
}"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
></div>
<div
v-if="isMessageReported"
class="reported"
>
<span v-once>{{ $t('reportedMessage') }}</span><br>
<span v-once>{{ $t('canDeleteNow') }}</span>
</div>
<hr>
<div
v-if="msg.id"
class="d-flex"
>
<div
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="userIsModerator && msg.flagCount"
class="message-hidden"
v-if="!isMessageReported"
class="action d-flex align-items-center"
@click="report(msg)"
>
{{ flagCountDescription }}
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
</div>
<div
class="card-body"
class="action d-flex align-items-center"
@click="remove()"
>
<user-link
v-if="!isSystemMessage"
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p
v-if="!isSystemMessage"
class="time"
>
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span><span
v-if="msg.username"
class="mr-1"
></span>
<span v-b-tooltip.hover="messageDate">{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">
({{ msg.client }})
</span>
</p>
<b-dropdown
v-if="!isSystemMessage"
right="right"
variant="flat"
toggle-class="with-icon"
class="card-menu no-min-width"
:no-caret="true"
>
<template #button-content>
<span
v-once
class="svg-icon inline menuIcon color"
v-html="icons.menuIcon"
>
</span>
</template>
<b-dropdown-item
class="selectListItem"
@click="copy(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.copy"
></span>
<span v-once>
{{ $t('copy') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canReportMessage"
class="selectListItem custom-hover--red"
@click="report(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.report"
></span>
<span v-once>
{{ $t('report') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canDeleteMessage"
class="selectListItem custom-hover--red"
@click="remove()"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.delete"
></span>
<span v-once>
{{ $t('delete') }}
</span>
</span>
</b-dropdown-item>
</b-dropdown>
<div
v-if="isSystemMessage"
class="system-message-body"
>
{{ msg.unformattedText }}
</div>
<div
v-else
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div
v-if="isMessageReported"
class="reported"
>
<span v-once>{{ $t('reportedMessage') }}</span><br>
<span v-once>{{ $t('canDeleteNow') }}</span>
<div v-once>
{{ $t('delete') }}
</div>
<like-button
v-if="canLikeMessage"
class="mt-75"
:liked-by-current-user="msg.likes[user._id]"
:like-count="likeCount"
@toggle-like="like()"
/>
</div>
</div>
</div>
</template>
<style lang="scss">
.message-card {
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
@@ -160,76 +76,43 @@
.at-text {
color: #6133b4;
}
.card-menu button {
justify-content: center;
margin: 0;
padding: 0;
height: 1rem;
width: 1rem;
}
.markdown p:last-of-type {
margin-bottom: 0;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.card {
background: transparent !important;
margin-bottom: 0 !important;
}
.action {
display: inline-block;
color: $gray-200;
margin-right: 1em;
font-size: 12px;
.message-card:not(.system-message) {
background: white;
}
:hover {
cursor: pointer;
}
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: $purple-500;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
.active {
color: $purple-300;
.svg-icon {
color: $purple-400;
.svg-icon {
color: $gray-300;
margin-right: .2em;
width: 16px;
}
}
}
.message-card {
border-radius: 7px;
margin: 0;
padding: 1rem 0.75rem 0.5rem 1rem;
.active {
color: $purple-300;
&.system-message {
padding-top: 0.5rem;
.svg-icon {
color: $purple-400;
}
}
.card-body {
position: relative;
padding: 0;
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
.time {
font-size: 12px;
color: $gray-100;
color: $gray-200;
margin-bottom: 0.5rem;
}
@@ -240,173 +123,49 @@
min-height: 0rem;
}
}
}
.card-menu {
position: absolute;
top: 0;
right: 0;
&:not(.show) {
display: none;
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
}
.card-body:hover {
.card-menu {
display: block;
.reported {
margin-top: 18px;
color: $red-50;
}
}
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.reported {
margin-top: 18px;
color: $red-50;
}
.selectListItem:not(:hover) .svg-icon.icon-16.color {
color: #{$gray-100}
}
.custom-hover--red {
--hover-color: #{$maroon-50};
--hover-background: #{rgba($red-500, 0.25)};
}
.user-sent-message {
border: 1px solid $purple-400;
}
.system-message {
border: 1px solid $purple-400;
}
.user-received-message {
border: 1px solid $gray-500;
}
.card-menu {
// icon-color is the menu icon itself
--icon-color: #{$gray-100};
--dropdown-item-hover-icon-color: #{$gray-100};
&:hover {
--icon-color: #{$purple-300};
}
}
.menuIcon {
width: 4px;
height: 1rem;
object-fit: contain;
}
.system-message-body {
line-height: 1.71;
text-align: center;
color: $purple-300;
}
</style>
<script>
import axios from 'axios';
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_FROM_SHADOW_MUTE, CHAT_FLAG_LIMIT_FOR_HIDING } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import { CopyToClipboardMixin } from '@/mixins/copyToClipboard';
import renderWithMentions from '@/libs/renderWithMentions';
import { mapState } from '@/libs/store';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import reportIcon from '@/assets/svg/report.svg';
import menuIcon from '@/assets/svg/menu.svg';
import { userStateMixin } from '@/mixins/userState';
import copyIcon from '@/assets/svg/copy.svg';
import LikeButton from '@/components/messages/likeButton.vue';
const LikeLogicMixin = {
computed: {
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatMessageId: this.privateMessageMode ? message.uniqueMessageId : message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
},
};
export default {
components: {
LikeButton,
userLink,
},
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
mixins: [
externalLinks, userStateMixin, LikeLogicMixin,
CopyToClipboardMixin,
],
mixins: [externalLinks],
props: {
msg: {
type: Object,
},
groupId: {
type: String,
},
privateMessageMode: {
type: Boolean,
},
userSentMessage: {
type: Boolean,
},
msg: {},
},
data () {
return {
icons: Object.freeze({
delete: deleteIcon,
report: reportIcon,
copy: copyIcon,
menuIcon,
}),
reported: false,
};
@@ -416,100 +175,19 @@ export default {
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
messageDateForSystemMessage () {
return this.isSystemMessage ? this.messageDate : '';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
userIsModerator () {
return this.hasPermission(this.user, 'moderator');
},
isSystemMessage () {
return this.msg.uuid === 'system';
},
canLikeMessage () {
if (this.isSystemMessage) {
return false;
}
if (this.privateMessageMode) {
return Boolean(this.msg.uniqueMessageId);
}
return this.msg.id;
},
canDeleteMessage () {
return this.privateMessageMode
|| this.msg.uuid === this.user._id
|| this.userIsModerator;
},
canReportMessage () {
if (this.privateMessageMode) {
return !this.isMessageReported;
}
return (this.user.flags.communityGuidelinesAccepted && this.msg.uuid !== 'system')
&& (!this.isMessageReported || this.userIsModerator);
},
isUserMentioned () {
const message = this.msg;
if (message.highlight) {
return true;
}
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
flagCountDescription () {
if (!this.msg.flagCount) {
return '';
}
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) {
return 'Message flagged once, not hidden';
}
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) {
return 'Message hidden';
}
return 'Message hidden (shadow-muted)';
},
},
mounted () {
this.$emit('message-card-mounted');
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
updated () {
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
methods: {
mapProfileLinksToModal () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
@@ -521,29 +199,16 @@ export default {
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: this.groupId,
groupId: 'privateMessage',
});
},
async remove () {
// eslint-disable-next-line no-alert
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) {
return;
}
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
const message = this.msg;
this.$emit('message-removed', message);
if (this.privateMessageMode) {
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
} else {
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
}
},
copy (message) {
this.mixinCopyToClipboard(message.text, this.$t('messageCopiedToClipboard'));
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);
@@ -1,9 +1,9 @@
<template>
<div
ref="container"
class="message-list"
class="container-fluid"
>
<div class="loadmore">
<div class="row loadmore">
<div v-if="canLoadMore && !isLoading">
<div class="loadmore-divider-holder">
<div class="loadmore-divider"></div>
@@ -28,7 +28,7 @@
<div
v-for="(msg) in messages"
:key="msg.id"
class="message-row"
class="row message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div
@@ -39,31 +39,28 @@
class="avatar-left"
:member="conversationOpponentUser"
:avatar-only="true"
:show-weapon="true"
:debug-mode="false"
:override-top-padding="'0'"
:override-top-padding="'14px'"
:hide-class-badge="true"
@click.native="showMemberModal(msg.uuid)"
/>
<message-card
:msg="msg"
:user-sent-message="user._id === msg.uuid"
:group-id="'privateMessage'"
:private-message-mode="true"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@message-card-mounted="itemWasMounted"
/>
<div
class="card"
:class="{'card-right': user._id !== msg.uuid, 'card-left': user._id === msg.uuid}"
>
<message-card
:msg="msg"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@message-card-mounted="itemWasMounted"
/>
</div>
<avatar
v-if="user && user._id === msg.uuid"
class="avatar-right"
:member="user"
:avatar-only="true"
:show-weapon="true"
:debug-mode="false"
:hide-class-badge="true"
:override-top-padding="'0'"
:override-top-padding="'14px'"
@click.native="showMemberModal(msg.uuid)"
/>
</div>
@@ -72,110 +69,121 @@
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors.scss';
.avatar-left, .avatar-right {
align-self: center;
::v-deep .character-sprites {
margin-bottom: -5px !important;
padding-bottom: 0 !important;
margin-top: -1px !important;
.avatar {
width: 170px;
min-width: 8rem;
height: 120px;
padding-top: 0 !important;
}
::v-deep .avatar {
margin-left: -1.75rem;
margin-right: -0.5rem;
}
}
.avatar-right {
margin-left: -1rem;
.card {
border: 0px;
margin-bottom: 1rem;
padding: 0rem;
width: 684px;
}
.message-list {
width: 100%;
padding-right: 10px;
margin-right: 0 !important;
}
.message-row {
margin-left: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
::v-deep .character-sprites {
margin-right: 1rem !important;
}
}
}
.hr {
width: 100%;
height: 20px;
border-bottom: 1px solid $gray-500;
text-align: center;
margin: 2em 0;
}
.card {
border: 0px;
margin-bottom: 1rem;
padding: 0rem;
width: 684px;
}
.message-row {
margin-left: 12px;
margin-right: 12px;
.hr-middle {
font-size: 16px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.5;
text-align: center;
color: $gray-200;
background-color: $gray-700;
padding: .2em;
margin-top: .2em;
display: inline-block;
width: 100px;
}
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
}
}
}
@media only screen and (max-width: 1200px) {
.card {
width: 100%;
}
}
.loadmore {
justify-content: center;
margin-right: 12px;
margin-top: 12px;
margin-bottom: 24px;
@media only screen and (min-width: 1400px) {
.message-row {
margin-left: -15px;
margin-right: -30px;
}
}
> div {
display: flex;
.card-left {
border: 1px solid $purple-500;
}
.card-right {
border: 1px solid $gray-500;
}
.hr {
width: 100%;
align-items: center;
height: 20px;
border-bottom: 1px solid $gray-500;
text-align: center;
margin: 2em 0;
}
button {
text-align: center;
color: $gray-50;
.hr-middle {
font-size: 16px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.5;
text-align: center;
color: $gray-200;
background-color: $gray-700;
padding: .2em;
margin-top: .2em;
display: inline-block;
width: 100px;
}
.loadmore {
justify-content: center;
margin-right: 12px;
margin-top: 12px;
margin-bottom: 24px;
> div {
display: flex;
width: 100%;
align-items: center;
button {
text-align: center;
color: $gray-50;
}
}
}
}
.loadmore-divider-holder {
flex: 1;
margin-left: 24px;
margin-right: 24px;
.loadmore-divider-holder {
flex: 1;
margin-left: 24px;
margin-right: 24px;
&:last-of-type {
margin-right: 0;
&:last-of-type {
margin-right: 0;
}
}
}
.loadmore-divider {
height: 1px;
border-top: 1px $gray-500 solid;
width: 100%;
.loadmore-divider {
height: 1px;
border-top: 1px $gray-500 solid;
width: 100%;
}
}
.loading {
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.loading {
padding-left: 1.5rem;
margin-bottom: 1rem;
}
</style>
@@ -272,9 +280,6 @@ export default {
// container.style.overflowY = 'scroll';
}
}, 50),
messageLiked (message) {
this.$emit('message-liked', message);
},
messageRemoved (message) {
this.$emit('message-removed', message);
},
@@ -692,7 +692,7 @@
<div class="form-inline clearfix">
<Sprite
class="pull-left"
:image-name="'inventory_quest_scroll_' + item.key"
:class="'inventory_quest_scroll_' + item.key"
style="margin-right: 10px"
/>
<p>{{ item.text() }}</p>
@@ -107,7 +107,7 @@ export default {
if (lastPublishedPost) this.posts.push(lastPublishedPost);
// If the user is authorized, show any draft
if (this.user && (this.user.permissions.news || this.user.permissions.fullAccess)) {
if (this.user && this.user.contributor.newsPoster) {
this.posts.unshift(
...postsFromServer
.filter(p => !p.published || moment().isBefore(p.publishDate)),
@@ -71,6 +71,7 @@
id="selectUser"
v-model="userSearchTerm"
:is-valid="foundUser._id"
:placeholder="$t('usernameOrUserId')"
:invalid-issues="userInputInvalidIssues"
/>
@@ -317,7 +318,6 @@ export default {
computed: {
...mapState({
currentEventList: 'worldState.data.currentEventList',
user: 'user.data',
}),
currentEvent () {
return find(this.currentEventList, event => Boolean(event.gemsPromo) || Boolean(event.promo));
@@ -399,8 +399,6 @@ export default {
this.foundUser = result;
}, 500),
selectUser () {
this.foundUser.g1g1 = this.currentEvent?.promo === 'g1g1'
&& this.foundUser._id !== this.user._id;
this.$root.$emit('habitica::send-gift', this.foundUser);
this.close();
},
@@ -59,12 +59,6 @@
<template v-if="paymentData.paymentType === 'gift-subscription'">
<div>
<span
v-if="paymentData.g1g1"
v-html="$t('paymentYouSentSubscriptionG1G1', {
name: paymentData.giftReceiver, months: paymentData.subscription.months})"
></span>
<span
v-else
v-html="$t('paymentYouSentSubscription', {
name: paymentData.giftReceiver, months: paymentData.subscription.months})"
></span>
@@ -147,17 +147,9 @@
<payments-buttons
v-if="userReceivingGift?._id"
:disabled="!subscription.key"
:stripe-fn="() => redirectToStripe({
gift,
uuid: userReceivingGift._id,
receiverName,
g1g1: userReceivingGift.g1g1,
})"
:stripe-fn="() => redirectToStripe({gift, uuid: userReceivingGift._id, receiverName})"
:paypal-fn="() => openPaypalGift({
gift: gift,
giftedTo: userReceivingGift._id,
receiverName,
g1g1: userReceivingGift.g1g1,
gift: gift, giftedTo: userReceivingGift._id, receiverName,
})"
/>
<payments-buttons
@@ -27,15 +27,27 @@
@changedPosition="tabSelected($event)"
>
<div slot="right-item">
<a
<div
v-once
id="petLikeToEatMarket"
class="drawer-help-text"
href="/static/faq#pet-foods"
target="_blank"
>
<span>{{ $t('petLikeToEat') }}</span>
</a>
<span>{{ $t('petLikeToEat') + ' ' }}</span>
<span
class="svg-icon inline icon-16"
v-html="icons.information"
></span>
</div>
<b-popover
target="petLikeToEatMarket"
:placement="'top'"
>
<div
v-once
class="popover-content-text"
v-html="$t('petLikeToEatText')"
></div>
</b-popover>
</div>
</drawer-header-tabs>
</div>
@@ -68,6 +80,7 @@
import _filter from 'lodash/filter';
import { mapState } from '@/libs/store';
import inventoryUtils from '@/mixins/inventoryUtils';
import svgInformation from '@/assets/svg/information.svg';
import Drawer from '@/components/ui/drawer';
import DrawerSlider from '@/components/ui/drawerSlider';
@@ -114,6 +127,10 @@ export default {
},
],
selectedDrawerTab: this.defaultSelectedTab,
icons: Object.freeze({
information: svgInformation,
}),
};
},
computed: {
@@ -31,6 +31,13 @@
&colon;&nbsp;
<a href="mailto:admin@habitica.com">admin&commat;habitica&period;com</a>
<br>
{{ $t('generalQuestionsSite') }}
&colon;&nbsp;
<a
target="_blank"
@click.prevent="openBugReportModal(true)"
> {{ $t('askQuestion') }}</a>
<br>
{{ $t('businessInquiries') }}
&colon;&nbsp;
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
@@ -47,8 +54,10 @@
<script>
import { mapState } from '@/libs/store';
import { goToModForm } from '@/libs/modform';
import reportBug from '@/mixins/reportBug.js';
export default {
mixins: [reportBug],
computed: {
...mapState({
user: 'user.data',
@@ -66,13 +66,16 @@
class="nav-link"
>{{ $t('presskit') }}</a>
</router-link>
<li class="nav-item">
<router-link
class="nav-item"
tag="li"
to="/static/contact"
>
<a
v-once
class="nav-link"
href="mailto:admin@habitica.com"
>{{ $t('contactUs') }}</a>
</li>
</router-link>
</ul>
<ul
v-else
+221 -358
View File
@@ -1,368 +1,231 @@
<template>
<!-- eslint-disable max-len -->
<div class="container-fluid">
<h1>Privacy Notice</h1>
<h1>HabitRPG Privacy Policy</h1>
<p class="strong pagemeta">
Last Updated September 19, 2022: Removed reference to Facebook login, which is no longer supported.
Last Updated June 20, 2024.
</p>
<p>
HabitRPG, Inc. (HabitRPG, we, us, or our) welcomes you. This privacy notice (the Privacy
Notice) describes how we process the information we collect about or from you through our Website
located at <a href="https://habitica.com/static/home">https://habitica.com/static/home</a> and/or our Apps
(our Digital Platforms), from our users, subscribers, visitors and other users of our technology and
platforms (together with our Digital Platforms, the Habitica Service or the Service), and when you
otherwise interact with us. This Privacy Notice may be updated by us from time to time without notice to
you. By accepting this Privacy Notice, accessing or using the Service, or otherwise manifesting your
assent to this Privacy Notice, you agree to be bound by this Privacy Notice. If you do not agree to (or
cannot comply with) all of the terms of this Privacy Notice, you may not access or use the Service.
This Privacy Policy applies when you interact with us through Habitica.com and any other feature or service owned or controlled by HabitRPG, Inc. (HabitRPG, we, or us) that posts or includes a valid link to this Privacy Policy (collectively, the Service(s)). This Privacy Policy informs you of our practices regarding the collection, use, and disclosure of personal information we receive from users of our Services. By accessing or using the Services, you consent to our Privacy Policy and our collection, use, and sharing of your information as described in this policy, our <a href="https://habitica.com/static/terms">Terms of Use</a>, and any additional policies and terms you may agree to in connection with the Services.
</p>
<p>
Capitalized terms not defined in this Privacy Notice shall have the meaning set forth in our Terms of
Service.
</p>
<h2>THE INFORMATION WE COLLECT AND HOW WE USE IT</h2>
<p>
In the course of operating the Service, HabitRPG collects or receives the following types of information
from visitors to the Website; users, subscribers or other users of its Platforms; and from third-party
integration partners, which may include personal information.
</p>
<h3>Contact Information</h3>
<p>
We collect contact information through our Service; contact information typically includes your name,
email address, and any other information you provide in messages to us. We use such contact information
for purposes such as providing you with information about the Service, responding to your inquiries,
sending you email alerts (including marketing emails), verifying your identity or providing you the
Service.
</p>
<h3>Account Access Information</h3>
<p>
In connection with the creation of an account on our Platforms, we collect account credentials such as
your email, username, and password. We use this account information to create your account, including to
verify your identity. We also use this information to manage your account, including your transactions. If
you choose to log into your account through Google or Apple, we capture and store the User ID and email
address connected to the respective account, so we can verify your identity when you log in.
</p>
<h3>User Content</h3>
<p>
As explained in more detail in the Terms of Service, HabitRPG allows you to upload and receive content,
including text, photos, images, task lists, graphics, artwork, links to outside content, and or other material.
It is your decision what kind of personal information (if any) you submit. We do not use personal
information posted in your content in any way except as needed to enforce the community guidelines and
terms of service or to provide assistance and troubleshooting issues with service to the account.
</p>
<h3>Transaction Information</h3>
<p>
When you make, or attempt to make, a purchase, sale or other transaction as a user or subscriber through
one of our Platforms, we may collect certain information from you, including your name, billing address,
mailing address, email address, and phone number. We refer to this information as Transaction
Information. We use the Transaction Information that we collect generally to facilitate transactions
between users through our Platforms (including providing you with invoices and/or transaction
confirmations). Additionally, we use this Transaction Information to: communicate with you; screen our
transactions for potential risk or fraud; and when in line with the preferences you have shared with us,
provide you with information or advertising relating to our products or Service. Our third-party payment
processing providers may also collect from you, process and store your payment information including
credit card information and/or bank account information in accordance with their respective privacy
policies linked to below:
</p>
<ul>
<li>
For Stripe, visit: <a
href="https://stripe.com/privacy"
target="_blank"
>https://stripe.com/privacy</a>
</li>
<li>
For Amazon Pay, visit: <a
href="https://pay.amazon.com/help/201751600"
target="_blank"
>https://pay.amazon.com/help/201751600</a>
</li>
<li>
For PayPal, visit: <a
href="https://www.paypal.com/us/webapps/mpp/ua/privacy-full"
target="_blank"
>https://www.paypal.com/us/webapps/mpp/ua/privacy-full</a>
</li>
<li>
For Apple Pay, visit: <a
href="https://www.apple.com/legal/privacy/data/en/apple-pay/"
target="_blank"
>https://www.apple.com/legal/privacy/data/en/apple-pay/</a>
</li>
<li>
For Google Pay, visit: <a
href="https://support.google.com/googlepay/answer/10223752?hl=en&co=GENIE.Platform%3DAndroid"
target="_blank"
>https://support.google.com/googlepay/answer/10223752?hl=en&co=GENIE.Platform%3DAndroid</a>
</li>
</ul>
<p>
We reserve the right to change our payment vendors at any time, or to use additional payment vendors, at
our discretion, and will update this Privacy Notice from time to time accordingly.
</p>
<h3>Server Log, Device and Other Technical Information</h3>
<p>
Our servers keep log files that record data each time a device accesses those servers. The log files may
contain data about the nature of such access, including the devices IP address, user agent string (e.g.,
operating system and browser type/version), and the pages you have clicked on while on our Service, and
details regarding your activity on the Service such as time spent on the Service and other performance and
usage data. We may use these log files for purposes such as assisting in monitoring and troubleshooting
errors and incidents, analyzing traffic, or optimizing the user experience.
</p>
<h3>Cookies and Similar Technologies</h3>
<p>
We may collect information using cookies, web beacons, tags, pixel and other similar technologies to
record information about how you use the Website, and to facilitate log-in and payments. Cookies are
small packets of data that a website stores on your computers or mobile devices hard drive (or other
storage medium) so that your computer will remember information about your use. We use both first
and third party session cookies and persistent cookies. Below is a general primer on session and persistent
cookies; information collected by cookies depends on its particular purpose. For more information, please
see the information regarding analytics providers discussed further below.
</p>
<ul>
<li>
<strong>Session Cookies</strong>: We use session cookies to make it easier for you to navigate our Service. A
session ID cookie expires when you close the Service.
</li>
<li>
<strong>Persistent Cookies</strong>: A persistent cookie remains on your device for an extended period of time or
until you delete it. Persistent cookies enable us to better understand how you interact with the Service and to
provide visitors with a better and more personalized experience by retaining information about their identity and
preferences, including but not limited to keeping them logged in even if the browser is closed.
</li>
</ul>
<p>
If you do not want us to place a cookie on your device, you may be able to turn that feature off on your
device. You may refuse to accept cookies from the Service at any time by activating the setting on your
browser which allows you to refuse cookies. Further information about the procedure to follow in order to
disable cookies can be found on your Internet browser providers website via your help screen. You may
wish to refer to <a
href="https://www.allaboutcookies.org/manage-cookies/index.html"
target="_blank"
>
https://www.allaboutcookies.org/manage-cookies/index.html</a> for information on commonly used browsers.
For more information about targeting and advertising cookies and how you can opt out, you can also visit
<a
href="https://optout.aboutads.info"
target="_blank"
>https://optout.aboutads.info</a>. Please be aware
that if cookies are disabled, not all features of the Service may operate properly or as intended.
</p>
<h3>Third-Party Analytics Providers</h3>
<p>
We use one or more thirdparty analytics Service to evaluate your use of the Service, as the case may be,
by compiling reports on activity (based on their collection of IP addresses, Internet service provider,
browser type, operating system and language, referring and exit pages and URLs, data and time, amount
of time spent on particular pages, what sections of the Service you visit, number of links clicked, search
terms and other similar usage data) and analyzing performance metrics. These third parties use cookies
and other technologies to help collect, analyze, and provide us reports or other data.
</p>
<p>
By accessing and using the Service, you consent to the processing of data about you by these analytics
providers in the manner and for the purposes set out in this Privacy Notice. For more information on these
third parties, including how to opt out from certain data collection, please visit the sites below. Please be
advised that if you opt out of any service, you may not be able to use the full functionality of the Service.
</p>
<ul>
<li>
For Google Analytics, visit: <a
href="https://marketingplatform.google.com/about/analytics/"
target="_blank"
>https://marketingplatform.google.com/about/analytics/</a>
</li>
<li>
For Amplitude, visit: <a
href="https://amplitude.com/privacy"
target="_blank"
>https://amplitude.com/privacy</a>
</li>
</ul>
<h3>Third-Party Advertisers/Remarketers</h3>
<p>
We may share or receive information about you with/from third parties, including, but not limited to,
advertising and remarketing providers, or similar partners, for purposes of personalizing or otherwise
understanding how you engage with ads or other content. These third parties may use cookies, pixel tags,
or other technologies to collect information in furtherance of such purposes, including to tailor, target
(i.e., behavioral, contextual, retargeting, and remarketing), analyze, report on, and/or manage advertising
campaigns or other initiatives. For example, when a browser visits a site, pixel tags enable us and these
third-parties to recognize certain cookies stored within the browser to learn which ads or other content
bring a user to a given site. Information that we may receive from these third-parties, including through
their service providers, may include advertising identifiers, IP addresses, reports, and campaign data.
</p>
<p>
By accessing and using the Service, you consent to the processing of data about you by these
advertisers/remarketing providers in the manner and for the purposes set out in this Privacy Notice.
</p>
<p>
For more information on our advertising partner Google AdMob, please visit <a
href="https://policies.google.com/privacy?hl=en"
target="_blank"
>https://policies.google.com/privacy?hl=en</a>.
</p>
<h3>Geolocation Information</h3>
<p>
We may, with your consent, automatically collect geolocation information from your device via your
browsers location Service. This consent may be provided by you on the device level (e.g., you have
consented to location Service generally through your browsers settings) or by accepting our request for
geolocation access on the Service. Please consult your browsers documentation regarding how to turn off
location Service. If you disable location Service, you may not be able to use the full array of features and
functionalities available through our Service.
</p>
<h3>Aggregate and De-identified Data</h3>
<p>
In an ongoing effort to better understand our users and the Service, we might analyze your information in
aggregate and/or de-identified form to operate, maintain, manage, and improve the Service. We may
share this aggregate or de-identified data with our affiliates, agents, and business partners. We may also
disclose aggregated or de-identified user statistics to describe the Service to current and prospective
business partners and to other third parties for other lawful purposes.
</p>
<h3>Onward Transfer to Third Parties</h3>
<ul>
<li>
Like many businesses, we hire other companies to perform certain business-related services. We
may disclose personal information to certain types of third party companies but only to the extent
needed to enable them to provide such service. The types of companies that may receive personal
information and their functions are: hosting service, technical assistance, database
management/back-up service, use analytics, marketing, and customer service.
</li>
<li>
To provide our Service and administer promotional programs, we may share your personal
information with our third-party promotional and marketing partners, including, without
limitation, businesses participating in our various programs.
</li>
<li>
We may also disclose personal information to our parent companies, subsidiaries, affiliates, joint
ventures, or other companies under common control to support the marketing and sale of our
products and Service.
</li>
</ul>
<h3>Business Transfers</h3>
<p>
In the event of a merger, dissolution, reorganization or similar corporate event, or the sale of all or
substantially all of our assets, we expect that the information that we have collected, including personal
information, would be transferred to the surviving entity in a merger or the acquiring entity. All such
transfers shall be subject to our commitments with respect to the privacy and confidentiality of such
personal information as set forth in this Privacy Notice. This Notice shall be binding upon HabitRPG and
its legal successors in interest.
</p>
<h3>Disclosure to Public Authorities</h3>
<p>
We are required to disclose personal information in response to lawful requests by public authorities,
including for the purpose of meeting national security or law enforcement requirements. We may also
disclose personal information to other third parties when compelled to do so by government authorities or
required by law or regulation including, but not limited to, in response to court orders and subpoenas.
</p>
<h2>UPDATES AND OPT-OUTS</h2>
<p>
On the website:<br>You can update your user profile on the Website by clicking the avatar box in the
upper left hand corner of the browser window, or by going to Settings and then selecting the Profile
option in the menu.
</p>
<p>
You can fully delete or reset your account via the Settings option on the Website.
</p>
<p>
On the mobile apps for iOS Versions 3.4.3 and Android versions 3.4.1.1 and below:
You can update your user profile on the mobile apps by tapping the Settings gear in the menu and then
selecting the Profile option.
</p>
<p>
You can fully delete or reset your account on the mobile apps by tapping the Settings gear in the menu
and then selecting the Authentication option.
</p>
<p>
If you would like us to fully delete your account and all data associated with it, please email us at
admin@habitica and we will handle your request within 30 days.
</p>
<p>
You may opt out at any time from the use of your personal information for direct marketing purposes by
emailing the instructions to <a href="mailto:admin@habitica.com">admin@habitica.com</a> or by clicking
on the Unsubscribe link located on the bottom of any HabitRPG marketing email and following the
instructions found on the page to which the link takes you. Please allow us a reasonable time to process
your request. You cannot opt out of receiving transactional e-mails related to the Service.
</p>
<h2>HOW WE PROTECT YOUR INFORMATION</h2>
<p>
HabitRPG takes very seriously the security and privacy of the personal information that it collects
pursuant to this Privacy Notice. Accordingly, we implement reasonable security measures designed to
protect your personal information from loss, misuse and unauthorized access, disclosure, alteration and
destruction, taking into account the risks involved in processing and the nature of such data, and to
comply with applicable laws and regulations. Please understand, however, that no security system is
impenetrable. We cannot guarantee the security of our databases or the databases of the third parties with
which we may share your information (as permitted herein), nor can we guarantee that the information
you supply will not be intercepted while being transmitted over the Internet. In particular, e-mail sent to
us may not be secure, and you should therefore take special care in deciding what information you send to
us via e-mail.
</p>
<h2>CHILDREN</h2>
<p>
The Service are intended for users 13 years or older; you are not permitted to access or use the Service if
you are younger than 13. We do not knowingly collect personal information from children under the age
of 13 through the Service. If you are under 13, please do not give us any personal information. We
encourage parents and legal guardians to monitor their childrens Internet usage and to help enforce our
Privacy Notice by instructing their children to never provide personal information without their
permission. If you have reason to believe that a child under the age of 13 has provided personal
information to us, please contact us at admin@habitica.com, and we will endeavor to delete that
information from our databases.
</p>
<h2>IMPORTANT NOTICE TO ALL NON-US RESIDENTS</h2>
<p>
Our servers are located in the US. Please be aware that your information may be transferred to, processed,
maintained, and used on computers, servers, and systems located outside of your state, province, country,
or other governmental jurisdiction where the privacy laws may not be as protective as those in your
country of origin. If you are located outside the United States and choose to use the Service, you do so at
your own risk.
</p>
<h2>CALIFORNIA PRIVACY RIGHTS</h2>
<p>
Pursuant to Section 1798.83 of the California Civil Code, residents of California have the right to obtain
certain information about the types of personal information that companies with whom they have an
established business relationship (and that are not otherwise exempt) have shared with third parties for
direct marketing purposes during the preceding calendar year, including the names and addresses of those
third parties, and examples of the types of Service or products marketed by those third parties. If you wish
to submit a request pursuant to Section 1798.83, please contact HabitRPG via email at
<a href="mailto:admin@habitica.com">admin@habitica.com</a>.
</p>
<h2>NEVADA PRIVACY RIGHTS</h2>
<p>
If you are a resident of Nevada, you have the right to opt-out of the sale of certain personal information to
third parties. You can exercise this right by contacting us at admin@habitica.com with the subject line
Nevada Do Not Sell Request and providing us with your name and the email address associated with
your account.
</p>
<h2>DO NOT TRACK</h2>
<p>
HabitRPG does not respond to Do Not Track settings or other related mechanisms on our Website at
this time.
</p>
<h2>LINKS TO EXTERNAL WEBSITES</h2>
<p>
The Service may contain links to third-party websites (<span style="text-decoration: underline;">External
Sites</span>). HabitRPG has no control over the privacy practices or the content of any such External Sites.
As such, we are not responsible for the content or the privacy policies of such External Sites. You should
check the applicable privacy notice or privacy policy and terms of use when visiting any such External Sites.
</p>
<h2>CHANGES TO THIS PRIVACY NOTICE</h2>
<p>
This Privacy Notice is effective as of the last updated date stated at the top of this Privacy Notice. We
may change this Privacy Notice from time to time with or without notice to you. By accessing the Service
after we make any such changes to this Privacy Notice, you are deemed to have accepted such changes.
Please be aware that, to the extent permitted by applicable law, our use of the information collected is
governed by the Privacy Notice in effect at the time we collect the information. Please refer back to this
Privacy Notice on a regular basis.
</p>
<h2>HOW TO CONTACT US</h2>
<p>
If you have questions about this Privacy Notice, please e-mail us at <a href="mailto:admin@habitica.com">
admin@habitica.com</a> with Privacy Notice in the subject line.
</p>
<address>
<strong>HabitRPG, Inc.</strong>
<br>202 Bicknell Ave., Ground Floor
<br>Santa Monica, CA 90405
<br>Email&colon;&nbsp;
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
</address>
<h2>Table of Contents</h2>
<ol>
<li><a href="#collection-of-information">Collection of Information</a></li>
<ul>
<li><a href="#information-you-provide-directly">Information You Provide Directly</a></li>
<li><a href="#information-we-collect-automatically">Information We Collect Automatically</a></li>
<li><a href=#location-data>Location Data</a></li>
<li><a href="#information-from-third-parties">Information from Third Parties</a></li>
</ul>
<li><a href="#purpose-and-use-of-information-we-collect">Purpose and Use of Information We Collect</a></li>
<li><a href="#sharing-of-information">Sharing of Information</a></li>
<li><a href="#third-party-analytics-providers">Third-Party Analytics Providers</a></li>
<li><a href="#security">Security</a></li>
<li><a href="#data-retention">Data Retention</a></li>
<li><a href="#general-audience-services">General Audience Services</a></li>
<li><a href="#consent-to-international-transfer">Consent to International Transfer</a></li>
<li><a href="#your-choices">Your Choices</a></li>
<li><a href="#changes-to-this-privacy-policy">Changes to This Privacy Policy</a></li>
<li><a href="#contact-us">Contact Us</a></li>
<li><a href="#jurisdiction-specific-rights">Jurisdiction-Specific Rights</a></li>
<ul>
<li><a href="#us-specific-rights">US Specific Rights</a></li>
<li><a href="#additional-notice-to-california-residents">Additional Notice to California Residents</a></li>
<li><a href="#additional-notice-to-nevada-residents">Additional Notice to Nevada Residents</a></li>
<li><a href="#notice-to-uk-eea-switzerland-residents">Notice to UK/EEA/Switzerland Residents</a></li>
</ul>
</ol>
<h3 id="collection-of-information">1. Collection of Information</h3>
<p>We and our third-party service providers and business partners may collect information from you directly and automatically when you visit the Services and from third parties. Some of this information may be considered personal information or personal data under various applicable laws. We consider information that identifies you as a specific, identified individual (such as your name, phone number, and e-mail address) to be personal information. We will also treat additional information, including IP addresses and cookie identifiers, as personal information or personal data where required by applicable law.</p>
<p>We may take your personal information and de-identify or pseudonymize it to make it non-personally identifiable, either by combining it with information about other individuals and/or by hashing the information or otherwise removing characteristics that make the information personally identifiable directly to you. We maintain and use de-identified or pseudonymized data without attempting to re-identify it, except where permitted by applicable law, such as to determine whether our de-identification processes satisfy legal requirements. We will treat de-identified or pseudonymized information as non-personal to the fullest extent allowed by applicable law.</p>
<h4 id="information-you-provide-directly">1.1 Information You Provide Directly</h4>
<p>We may ask you to provide certain personal information when you interact with the Services. This information may include contact information (such as your name and email), account information (such as your email - and if you choose to log in through Google or Apple, the associated user ID and email address), transaction information (such as your billing address and mailing address), or user content you choose to upload (such as photos and task lists). Note that all payments are handled by our third-party payment providers who may collect relevant information in order to complete your transaction (such as your payment card, billing address, and phone number) and are subject to its privacy policy, as provided at the time of such collection.</p>
<p>We may also ask you to provide the contact information of another individual, such as when you invite another user to the Services. When you provide us with an individuals contact information in this context, we will only use this information for the specific reason for which it was provided.</p>
<h4 id="information-we-collect-automatically">1.2 Information We Collect Automatically</h4>
<p>We and third-party companies and business partners may use a variety of technologies that automatically or passively collect certain information whenever you visit our Services or otherwise interact with us or our content (<strong>Usage Information</strong>). Usage Information may include the hardware model, browser, and operating system you are using, the URL or advertisement that referred you to the Service you are visiting, all of the areas within the Services that you visit, your time zone, non-precise location information, and mobile network (if applicable), among other information. In addition, we automatically collect your IP address or other unique identifier (<strong>Device Identifier</strong>) for any computer, mobile phone or other device you use to access our Services. In some cases, we may directly collect location information through your device. You may be able to turn off the collection of location information through the settings on your device. Usage Information is generally non-identifying, but if HabitRPG associates it with you as a specific and identifiable person, HabitRPG treats it as personal information.</p>
<ul>
<li><em>Web Tags</em>: Small graphic images or other web programming code called web tags (also known as pixel tags, 1x1 GIFs, or clear GIFs) may be included in our email messages sent by our third-party service providers on our behalf. Web beacons may be invisible to you, but any electronic image or other web programming code inserted into a web page or email can act as a web beacon. Web beacons or similar technologies may be used for several purposes, including, without limitation, to count visitors to the Service, to monitor how users navigate the Service, to count how many emails that were sent were opened, or to count how many particular links were actually viewed.</li>
<li><em>Embedded Scripts</em>: An embedded script is a programming code that is designed to collect information about your interactions with the Services, such as the links you click on. The code is temporarily downloaded onto your device from our server or a third-party service.</li>
<li><em>Session Events</em>: HabitRPG may engage third party service providers to analyze your session events with user interfaces, including what pages you visit on the Service, how long you visit those pages, the links you click, and your path through the Service for website analytics purposes, solely for our internal business purposes, and to improve our Services.</li>
</ul>
<h4 id="location-data">1.3 Location Data</h4>
<p>We do not collect your precise location. However, please note that we may still be able to collect or infer your approximate location through other information we collect, such as IP address. In addition, some mobile service providers may also provide us or our third-party service providers with information regarding the non-precise physical location of the device used to access our Services.</p>
<h4 id="information-from-third-parties">1.4 Information from Third Parties</h4>
<p>We may receive information about you from third parties. You may have the opportunity to log in through or otherwise connect your Apple and Google accounts. Additionally, when you interact with us through social media, you will be choosing to share information about your interactions with HabitRPG with that social media service.</p>
<p>The following chart sets out by category the personal data collected (<strong>Category</strong>), the purposes for which the information is collected (<strong>A. Purposes</strong>), the categories of third parties to whom the information may be disclosed for a business purpose (<strong>B. Disclosed To</strong>), and the categories of third parties to whom the information may be sold for monetary value or other valuable consideration or shared for cross-context behavioral advertising/targeted marketing (<strong>C. Sold/Shared To</strong>).</p>
<p>CHART GOES HERE</p>
<h3 id="purpose-and-use-of-information-we-collect">2. Purpose and Use of Information We Collect</h3>
<p>We may use non-personal information for any purpose, including for research and marketing purposes. We also use information that we collect, including personal information and Usage Information, as disclosed in this Privacy Policy and as follows:</p>
<ul>
<li>to provide the Services to you and allow you to participate in the features we offer;</li>
<li>to verify your identity and to otherwise manage your user account;</li>
<li>to tailor and target content, recommendations, and offers we display to you on the Services to send you communications with information about our products, and Services;</li>
<li>to fulfill your order, send you an order confirmation, process your payment, and communicate with you about your order;</li>
<li>to respond to your inquiries, customer service questions, feedback, or requests;</li>
<li>to provide you with technical support;</li>
<li>to improve our Services and for legal, regulatory, and internal business purposes; and</li>
<li>to fulfill any other purpose consistent with this Privacy Policy.</li>
</ul>
<p>We may also use your personal information for any other purpose as disclosed to you at the time of collection and with your consent. For example, if we choose to begin offering sweepstakes promotions, we will notify you of the use of your personal information for such sweepstakes prior to your engagement.</p>
<h3 id="sharing-of-information">3. Sharing of Information</h3>
<p>HabitRPG may share non-personal information, such as information about use of our Services, aggregated user statistics, or hashed and other de-identified or pseudonymized information with third parties, in our discretion. We do not share personal information with unaffiliated third parties for those third parties' own marketing purposes without your consent.</p>
<p>In addition, we may share the information we have collected about you as disclosed at the time you provide your information or your consent, and as described elsewhere in this Privacy Policy, including:</p>
<ul>
<li><em>At Your Request/Publicly Posted Content.</em> We may share information when you direct us to do so, such as if you choose to submit content on the Service (for example, when you decide to share your habits with other users). We do not control the actions of third parties, and you post content and share your information at your own risk.</li>
<li><em>Service Providers.</em> Our service providers may collect information on our behalf and at our direction, in order to provide Services on our behalf to help with our business activities. These companies are authorized to use your personal information only as necessary to provide these Services to us.<br><br>
Our Service Providers include:<br><br>
<table>
<tr>
<th>Service Provider Name</th>
<th>Product(s)</th>
</tr>
<tr>
<td>Google Cloud</td>
<td>cloud computing; storage</td>
</tr>
<tr>
<td>MongoDB</td>
<td>database</td>
</tr>
<tr>
<td>Heroku</td>
<td>cloud-based testing</td>
</tr>
<tr>
<td>Amazon Web Services</td>
<td>content storage</td>
</tr>
<tr>
<td>Hetzner</td>
<td>translations and push notifications</td>
</tr>
<tr>
<td>Stripe</td>
<td>payment processing</td>
</tr>
<tr>
<td>PayPal</td>
<td>payment processing</td>
</tr>
<tr>
<td>Amazon Payments</td>
<td>payment processing</td>
</tr>
<tr>
<td>Apple App Store</td>
<td>app host</td>
</tr>
<tr>
<td>Google Play Store</td>
<td>app host</td>
</tr>
<tr>
<td>Mailchimp</td>
<td>email marketing</td>
</tr>
<tr>
<td>Gmail</td>
<td>internal communications</td>
</tr>
<tr>
<td>Redislabs</td>
<td>rate limiting</td>
</tr>
<tr>
<td>Loggly</td>
<td>log management and analytics</td>
</tr>
<tr>
<td>Slack</td>
<td>internal communications</td>
</tr>
<tr>
<td>Amplitude</td>
<td>analytics</td>
</tr>
</table>
<br>
</li>
<li><em>Sweepstakes, Contests and Promotions.</em> We may offer sweepstakes, contests, or other promotions (any of which, a <strong>Promotion</strong>) that may require registration. By participating in a Promotion, you are agreeing to the provisions, conditions, or official rules that govern the Promotion, which may contain specific requirements of you (including, except where prohibited by law, allowing the sponsor(s) of the Promotion to use your name, voice, likeness, or other indicia of persona in advertising or marketing materials). If you choose to enter a Promotion, personal information may be disclosed to co-promotion partners, third parties or the public in connection with the administration of such Promotion, including in connection with winner selection, prize fulfillment, as required by law, or as permitted by the Promotions terms or official rules.</li>
<li><em>Business Transitions.</em> By providing your personal information you understand if there is a corporate transition such as a merger, acquisition, bankruptcy, or sale of all or a portion of our assets, or during the course of any due diligence process, your personal information may be disclosed without your further consent. Where required by law, we will make reasonable efforts to notify you before such transfer.</li>
<li><em>Administrative and Legal Reasons.</em> We reserve the right to use or disclose any information as needed to satisfy or fulfill our obligations under any law, regulation or legal request; to protect the integrity of the HabitRPG; to fulfill your requests; to cooperate in a law enforcement investigation, an investigation on a public safety matter, or an investigation into claims of intellectual property infringement; to protect and defend the legal rights and/or property of HabitRPG and any of our affiliates, shareholders, or our Services, any of its users, or any other party; or, in an emergency, to protect the health and safety of users or the general public. </li>
</ul>
<h3 id="third-party-analytics-providers">4. Third Party Analytics Providers</h3>
<p>We work with third-party service providers, such as analytics providers, to provide us with information regarding use of and traffic on our Services (including without limitation the pages viewed and the actions users take when visiting our Services). These third parties may set and access their own tracking technologies on your device (including cookies), and they may otherwise collect or have access to certain information about your use of the Services (such as Usage Information and Device Identifier). Some of these parties may collect personal information over time when you visit our Services or other online websites, and some may connect the information they collect through cookies and with other information about you.</p>
<ul>
<li>Google Analytics. We use Google Analytics, which uses cookies and similar technologies to collect and analyze data about the use of the Services and report on activities and trends. This service may also collect data about the use of other websites, apps, and online services. You can <a href="https://policies.google.com/technologies/partner-sites">learn about Google's practices</a>, and opt out of them by downloading the <a href="https://tools.google.com/dlpage/gaoptout">Google Analytics opt-out browser add-on.</a></li>
</ul>
<p>Options for Our Mobile App: Mobile devices may contain settings that allow you to disable certain tracking analytics. If this is available, you can opt-out through your mobile device settings.</p>
<h3 id="security">5. Security</h3>
<p>HabitRPG uses commercially reasonable steps designed to secure your personal information; however, no data transmission over the Internet, wireless transmission, or electronic storage of data can be guaranteed to be 100% secure. HabitRPG cannot ensure or warrant the security of any data we collect. You use the Services and provide us your data at your own risk.</p>
<h3 id="data-retention">6. Data Retention</h3>
<p>We will only retain your personal information for as long as your account is active or as necessary to provide you Services, comply with our legal obligations, resolve disputes, and enforce our agreements.</p>
<h3 id="general-audience-services">7. General Audience Services</h3>
<p>The Service are intended for users 13 years or older; you are not permitted to access or use the Service if you are younger than 13. We do not knowingly collect personal information from children under the age of 13 through the Service. We encourage parents and legal guardians to monitor their childrens Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 13 has provided personal information to us, please contact us at privacy@habitica.com, and we will delete that information from our databases.</p>
<h3 id="consent-to-international-transfer">8. Consent to International Transfer</h3>
<p>HabitRPG is based in the United States. Please be aware that information we collect will be transferred to and processed in the United States and other countries. HabitRPG makes no representation that this Privacy Policy or the practices described in it comply with the laws of any other jurisdiction. By using the Services, or providing us with any information, you fully understand and unambiguously consent to this transfer, processing, and storage of your information in the United States and other jurisdictions for which the privacy laws may not be as comprehensive as those in the country where you reside and/or are a citizen. As a result, this information may be subject to access requests from governments, courts, or law enforcement in the United States and other countries according to laws in those jurisdictions.</p>
<h3 id="your-choices">9. Your Choices</h3>
<p><em>Edit Your Information</em>: On the website, you can update the information in your user profile at any time by going to the user icon in the upper right, selecting the Profile option, then clicking Edit Profile. You can update your username and email address by going to the user icon in the upper right and selecting the Settings option. If you are using the mobile app, you can update your user profile, username, and email by tapping the Settings gear in the menu and then selecting the My Account option.</p>
<p><em>Reset Your Account</em>: You can fully delete or reset your account by selecting the user icon in the upper right, selecting the Settings, and then looking under General Settings. You can fully delete or reset your account on the mobile apps by tapping the Settings gear in the menu and then selecting the My Account submenu. Please note that in order to fully delete all data associated with your account, you will need to email us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>. Note that we may be required to retain certain data about you to comply with applicable laws.</p>
<p><em>Newsletter</em>: You may also sign-up to receive our email newsletter. If you would like to discontinue receiving this information, you may update your email preferences by using the Unsubscribe link found in emails we send to you, or by contacting us. Please note that we reserve the right to send you certain communications relating to your account or use of the Services, such as administrative and services announcements. These transactional account messages may be unaffected if you choose to opt out from marketing e-mails.</p>
<p><em>Push Notifications</em>: With your consent, we may send promotional and non-promotional push notifications or alerts to your mobile device. You can elect to stop receiving those messages by changing the notification settings in the app or on your mobile device.</p>
<p><em>Other Privacy Rights</em>: Certain jurisdictions provide additional rights. Please see the <a href="#jurisdiction-specific-rights">Jurisdiction-Specific Rights</a> section below for more information.</p>
<h3 id="changes-to-this-privacy-policy">10. Changes to This Privacy Policy</h3>
<p>To the extent permitted by applicable law, we reserve the right to change or modify this Privacy Policy at our discretion at any time. We will notify you of any material changes by posting the changed or modified Privacy Policy on our Services. We may also provide notice to you in other ways, such as through contact information you have provided. Any changes will be effective immediately upon the posting of the revised Privacy Policy unless otherwise specified. Your continued use of the Services after the effective date of the revised Privacy Policy (or such other act as specified in the revised Privacy Policy) will, to the fullest extent permitted by applicable law, constitute your consent to those changes. However, we will provide notice and obtain your consent (opt-in or opt-out) if required by law. We encourage you to regularly review this Privacy Policy for the latest information on our privacy practices.</p>
<h3 id="contact-us">11. Contact Us</h3>
<p>If you have any questions or concerns about this Privacy Policy, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a> with Privacy Policy in the subject line. You may also write to us at:</p>
<address>
HabitRPG, Inc.
<br>202 Bicknell Ave., Ground Floor
<br>Santa Monica, CA 90405
</address>
<h3 id="jurisdiction-specific-rights">12. Jurisdiction-Specific Rights</h3>
<p>As set forth below, residents of certain jurisdictions may have additional rights and choices regarding their personal information. If you are a resident of Nevada, please see additional information <a href="#additional-notice-to-nevada-residents">here</a>. Residents of the US please see additional information <a href="#us-specific-rights">here</a>. Residents of the UK, Switzerland, and EEA, please see additional information <a href="#notice-to-uk-eea-switzerland-residents">here</a>.</p>
<ul id="us-specific-rights">
<li>
<p><strong>US Specific Rights</strong>. Residents of U.S. states have the ability to exercise additional rights and choices regarding their personal data. We will take reasonable steps to accommodate your request but may need to verify your identity before doing so.<br><br>
We set forth above the categories of personal data we process, the purpose for processing personal data, the categories of personal data shared, and the categories of third parties with whom personal data is shared. If you would like to exercise your applicable rights, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a> to submit a request.</p>
</li>
<ul>
<li><strong>Access Your Personal Data/Data Portability &mdash; </strong>You have the right to confirm whether we are processing your personal data and request to access such data, obtain a copy of the personal data previously provided by you to us and, to the extent feasible, in a readily usable format to allow data portability.</li>
<li><strong>Delete Your Personal Data &mdash; </strong>You have the Right to Delete your personal data.</li>
<li><strong>Correct Your Personal Data &mdash; </strong>You have the Right to Correct the personal data we hold about you.</li>
<li><strong>Opt-Out of Sales of Your Personal Data and Targeted Advertising &mdash; </strong>You have the right to opt out of the sale of your data. Note that HabitRPG does not sell personal data to third parties for monetary compensation, but may share personal data for other valuable consideration such as with our third party analytics providers. If you would like to opt out of the sale of your personal data, please adjust your settings in our Cookie Preference Center. <strong><em>[LINK NEEDED]</em></strong></li>
<li><strong>Right to Appeal &mdash; </strong>If, for any reason, you would like to appeal our decision relating to your request, you have the right to submit an appeal if your state permits such right. Please include your full name, the basis for your appeal, and any additional information to consider.</li>
<li><strong>Opt-Out of Profiling in furtherance of legal or similarly significant effects &mdash; </strong>HabitRPG does not process your personal data for the purposes of profiling in furtherance of decisions that produce legal or similarly significant effects.</li>
</ul>
</ul>
<ul id="additional-notice-to-california-residents">
<li><strong>Additional Notice to California Residents.</strong></li>
<p>HabitRPG does not share personal information with third parties for their direct marketing purposes (as defined by California Civil Code Section 1798.83). If you are a California consumer and you have questions about our practices, please send your request by email to <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>. You must put the statement Your California Privacy Rights in the subject field of your email. We are not responsible for notices that are not labeled or sent properly, or do not have complete information.</p>
</ul>
<ul id="additional-notice-to-nevada-residents">
<li><strong>Additional Notice to Nevada Residents.</strong></li>
<p>HabitRPG does not currently sell your covered information as those terms are defined under applicable Nevada law. You may still submit an opt-out request and we will honor that request as required by Nevada law if HabitRPG were to engage in such a sale in the future. You may do so by emailing us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a> and putting Your Nevada Privacy Rights in the subject field of your request.</p>
</ul>
<ul id="notice-to-uk-eea-switzerland-residents">
<li><strong>Notice to United Kingdom/European/Switzerland Residents.</strong></li>
<p>If you are a resident of the United Kingdom (UK), European Economic Area (EEA), or of Switzerland, the following information applies.</p>
<p><strong>Purposes of processing and legal basis for processing: </strong>As explained above, we process personal information in various ways depending upon your use of our Services. We process personal information on the following legal bases: (1) with your consent; (2) as necessary to perform our agreement to provide the Services; (3) compliance with our legal obligations; and (4) as necessary for our legitimate interests in providing the Service where those interests do not override your fundamental rights and freedoms related to data privacy such as for:</p>
<ul>
<li>preventing fraud;</li>
<li>ensuring network and information security, including preventing unauthorized access to our computer and electronic communication systems and preventing malicious software distribution;</li>
<li>supporting internal administration;</li>
<li>improving and developing the Services; and</li>
<li>conducting data analytics analyses to review and better understand consumer interaction.</li>
</ul><br>
<p><strong>Right to lodge a complaint: </strong>Users that reside in the UK, EEA, or Switzerland have the right to seek information and assistance or lodge a complaint about our data collection and processing actions with the supervisory authority where they reside. Contact details for data protection authorities are available here. UK: <a href="https://ico.org.uk/">https://ico.org.uk/</a>, EEA: <a href="ttps://edpb.europa.eu/about-edpb/board/members_en">https://edpb.europa.eu/about-edpb/board/members_en</a> Switzerland: <a href="https://www.edoeb.admin.ch/edoeb/en/home/deredoeb/kontakt.html">//www.edoeb.admin.ch/edoeb/en/home/deredoeb/kontakt.html</a>.</p>
<p><strong>Transfers:</strong>Personal information we collect may be transferred to, and stored and processed in, the United States or any other country in which we or our affiliates or subcontractors maintain facilities. Transfers of personal data to a third country without an adequacy decision (as that term is understood pursuant to Article 45 of GDPR) are required to be subject to appropriate safeguards such as standard contractual clauses. In certain cases, we rely on your consent to facilitate transfer, processing, and storage of your data in the United States and other jurisdictions, where laws regarding processing of personal information may be less stringent than the laws in the EEA, UK, and Switzerland.</p>
<p><strong>Withdraw consent: </strong>If we have collected personal information with your consent, you have the right to withdraw that consent at any time.</p>
<p><strong>Access: </strong>You have the right to request access to personal information we collected about you and information about its sources, purposes, and sharing.</p>
<p><strong>Correction: </strong>You have the right to request that we correct the personal information we hold about you if it is inaccurate or incomplete.</p>
<p><strong>Erasure: </strong>You have the right to request that we erase data we have collected from you. Please note that we may have a reason to deny your deletion request or delete data in a more limited way than you anticipated, e.g., because of a legal obligation to retain it.</p>
<p><strong>Portability:</strong>You have the right, in certain circumstances, to request that we provide your personal information to you in a format that can be transferred to another entity.</p>
<p><strong>Restrict Processing: </strong>You have the right, in certain circumstances, to request that we limit our processing of your personal information if you are (1) contesting the accuracy of your personal information, (2) asserting that our processing is unlawful; (3) asserting that we no longer need to keep the information for reasons related to the establishment, exercise, or defense of legal claims, or you object to our processing. You have the right, in certain circumstances, to request that we limit our processing of your personal information if you are contesting the accuracy of your personal information; asserting that our processing is unlawful; asserting that we no longer need to keep the information for reasons related to the establishment, exercise, or defense of legal claims, or you object to our processing</p>
<p><strong>Objection: </strong>You have the right to object to our processing if we are processing your personal information based on legitimate interests, using your personal information for direct marketing (including profiling), or processing your personal information for purposes of scientific or historical research and statistics.</p>
<p><strong>Verification Procedures: </strong>We must verify your identity for everyones protection, so we may require you to provide us with verification information prior to accessing any records containing personal information about you. We do this by asking you to provide personal identifiers we can match against information we may have collected from you previously and confirm your request using the email stated in the request.</p>
<p>We will use the information you provide for verification only for the purpose of verification. We may have a reason under the law why we do not have to respond to your request or respond to it in a more limited way than you anticipated. If we do, we will explain that to you in our response.</p>
</ul>
</div>
<!-- eslint-enable max-len -->
</template>
@@ -135,7 +135,7 @@
}
}
.bluesky svg {
.twitter svg {
background-color: $purple-50;
fill: $purple-500;
&:hover {
@@ -86,7 +86,7 @@
>
<a
target="_blank"
href="https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica"
href="https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet"
:class="cssClass('headings')"
>{{ $t('markdownHelpLink') }}</a>
</small>
@@ -129,12 +129,6 @@
padding-top: 6px;
padding-left: 24px;
padding-right: 24px;
a {
line-height: 1.33;
color: $gray-500;
font-weight: normal;
}
}
.drawer-tab {
@@ -20,7 +20,6 @@
}"
>
<input
ref="textInput"
:value="value"
class="form-control"
:type="inputType"
@@ -30,23 +29,19 @@
}"
:readonly="readonly"
:aria-readonly="readonly"
autocomplete="off"
:placeholder="placeholder"
@keyup="handleChange"
@keyup.enter="$emit('enter')"
@blur="$emit('blur')"
>
</div>
<template v-if="!hideErrorLine">
<div
v-for="issue in invalidIssues"
:key="issue"
class="input-error"
>
{{ issue }} &nbsp;
</div>
</template>
<div
v-for="issue in invalidIssues"
:key="issue"
class="input-error"
>
{{ issue }} &nbsp;
</div>
</div>
</div>
</template>
@@ -90,10 +85,6 @@ export default {
type: Array,
default: () => [],
},
hideErrorLine: {
type: Boolean,
default: false,
},
},
data () {
return {
@@ -116,9 +107,6 @@ export default {
this.wasChanged = true;
this.$emit('update:value', value);
},
focus () {
this.$refs.textInput.focus();
},
},
};
</script>
@@ -140,12 +128,4 @@ export default {
margin-bottom: 0;
}
/* this removes safari "save username" UI, we only search for one, we dont want to save it */
input::-webkit-contacts-auto-fill-button,
input::-webkit-credentials-auto-fill-button {
visibility: hidden;
position: absolute;
right: 0;
}
</style>
+13 -4
View File
@@ -29,12 +29,20 @@
@import '~@/assets/scss/colors.scss';
.user-link { // this is the user name
font-family: 'Roboto Condensed', sans-serif;
font-weight: bold;
margin-bottom: 0;
cursor: pointer;
font-size: 14px;
line-height: 1.71;
display: inline-flex !important;
display: inline-block;
font-size: 16px;
// currently used in the member-details-new.vue
&.smaller {
font-family: Roboto;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
}
&.no-tier {
color: $gray-50;
@@ -103,6 +111,7 @@ export default {
'backer',
'contributor',
'hideTooltip',
'smallerStyle',
'showBuffed',
'context',
],
@@ -164,7 +173,7 @@ export default {
return this.hideTooltip ? '' : achievementsLib.getContribText(this.contributor, this.isNPC) || '';
},
levelStyle () {
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)}`;
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)} ${this.smallerStyle ? 'smaller' : ''}`;
},
},
};
@@ -979,7 +979,6 @@
import moment from 'moment';
import axios from 'axios';
import each from 'lodash/each';
import find from 'lodash/find';
import cloneDeep from 'lodash/cloneDeep';
import achievementsLib from '@/../../common/script/libs/achievements';
import Content from '@/../../common/script/content';
@@ -1063,12 +1062,8 @@ export default {
},
computed: {
...mapState({
currentEventList: 'worldState.data.currentEventList',
flatGear: 'content.gear.flat',
}),
currentEvent () {
return find(this.currentEventList, event => Boolean(event.promo));
},
userJoinedDate () {
return moment(this.user.auth.timestamps.created)
.format(this.userLoggedIn.preferences.dateFormat.toUpperCase());
@@ -1262,7 +1257,6 @@ export default {
},
openSendGemsModal () {
this.user.g1g1 = this.currentEvent?.promo === 'g1g1';
this.$store.state.giftModalOptions.startingPage = 'buyGems';
this.$root.$emit('habitica::send-gift', this.user);
},
@@ -1,102 +0,0 @@
import debounce from 'lodash/debounce';
export const autoCompleteHelperMixin = {
data () {
return {
mixinData: {
autoComplete: {
caretPosition: 0,
coords: {
TOP: 0,
LEFT: 0,
},
},
},
};
},
methods: {
autoCompleteMixinHandleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
autoCompleteMixinHandleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
autoCompleteMixinSelectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
autoCompleteMixinSelectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
autoCompleteMixinSelectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
autoCompleteMixinUpdateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
autoCompleteMixinResetCoordsPosition () {
this.mixinData.autoComplete.coords = {
TOP: 0,
LEFT: 0,
};
},
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
_getCoord (e, text) {
const caretPosition = text.selectionEnd;
this.mixinData.autoComplete.caretPosition = caretPosition;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, caretPosition);
span.textContent = text.value.substr(caretPosition) || '.';
div.appendChild(span);
this.mixinData.autoComplete.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this._getCoord(eventUpdate, text);
},
},
};
+3 -5
View File
@@ -1,7 +1,7 @@
import { NotificationMixins } from './notifications';
import notifications from './notifications';
export const CopyToClipboardMixin = {
mixins: [NotificationMixins],
export default {
mixins: [notifications],
methods: {
async mixinCopyToClipboard (valueToCopy, notificationToShow = null) {
if (navigator.clipboard) {
@@ -21,5 +21,3 @@ export const CopyToClipboardMixin = {
},
},
};
export default CopyToClipboardMixin;
-8
View File
@@ -71,7 +71,6 @@ export default {
giftData,
gemsBlock,
sku,
g1g1,
} = data;
let { url } = data;
@@ -81,10 +80,6 @@ export default {
paymentType: type,
};
if (type === 'gift-subscription') {
appState.g1g1 = g1g1;
}
if (type === 'subscription') {
appState.subscriptionKey = this.subscriptionPlan || this.subscription.key;
}
@@ -169,9 +164,6 @@ export default {
paymentCompleted: false,
paymentType,
};
if (paymentType === 'gift-subscription') {
appState.g1g1 = data.g1g1;
}
if (paymentType === 'subscription') {
appState.subscriptionKey = sub.key;
} else if (paymentType === 'groupPlan') {
@@ -1,64 +0,0 @@
<template>
<div
v-if="filtersConversations.length > 0"
class="conversations"
>
<conversation-item
v-for="conversation in filtersConversations"
:key="conversation.key"
:active-key="selectedConversation?.key"
:contributor="conversation.contributor"
:backer="conversation.backer"
:uuid="conversation.key"
:display-name="conversation.name"
:username="conversation.username"
:last-message-date="conversation.date"
:last-message-text="conversation.lastMessageText
? removeTags(parseMarkdown(conversation.lastMessageText)) : ''"
@click="selectConversation(conversation.key)"
/>
</div>
</template>
<style scoped lang="scss">
.conversations {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
</style>
<script>
import { defineComponent } from 'vue';
import habiticaMarkdown from 'habitica-markdown';
import conversationItem from '@/pages/private-messages/pm-conversation-item.vue';
export default defineComponent({
components: { conversationItem },
props: {
filtersConversations: {
type: Array,
default: () => [],
},
selectedConversation: {
type: Object,
default: null,
},
},
methods: {
removeTags (html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
},
parseMarkdown (text) {
if (!text) return null;
return habiticaMarkdown.render(String(text));
},
selectConversation (conversationKey) {
this.$emit('selectConversation', conversationKey);
},
},
});
</script>
@@ -1,37 +0,0 @@
<template>
<div
class="pm-disabled-caption text-center"
>
<h4>{{ disabledTexts.title }}</h4>
<p>{{ disabledTexts.description }}</p>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.pm-disabled-caption {
padding-top: 1.5em;
z-index: 2;
h4, p {
color: $gray-200;
}
h4 {
margin-top: 0;
margin-bottom: 0.4em;
}
p {
font-size: 12px;
margin-bottom: 0;
}
}
</style>
<script>
export default {
props: ['disabledTexts'],
};
</script>
@@ -1,71 +0,0 @@
<template>
<div
class="empty-messages m-auto text-center empty-sidebar"
>
<div class="no-messages-box">
<div
v-once
class="svg-icon envelope mb-4"
v-html="icons.mailIcon"
></div>
<strong
v-once
class="mb-1"
>
{{ $t('emptyMessagesLine1') }}
</strong>
<p v-if="!chatRevoked">
{{ $t('emptyMessagesLine2') }}
</p>
</div>
<button
class="btn btn-primary mt-4 d-flex align-items-center"
@click="$emit('newMessageClicked')"
>
<div
class="svg-icon icon-10 color mr-2"
v-html="icons.positive"
></div>
{{ $t('newMessage') }}
</button>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
strong {
line-height: 1.71;
color: $gray-100;
}
.svg-icon.icon-10 {
margin: 3px;
}
p {
font-size: 14px;
font-weight: 400;
line-height: 24px;
}
</style>
<script>
import mailIcon from '@/assets/svg/mail.svg';
import positiveIcon from '@/assets/svg/positive.svg';
export default {
props: {
chatRevoked: Boolean,
},
data () {
return {
icons: Object.freeze({
mailIcon,
positive: positiveIcon,
}),
};
},
};
</script>
@@ -1,69 +0,0 @@
<template>
<div
v-once
class="centered empty-messages m-auto text-center"
>
<avatar
v-if="memberObj"
:member="memberObj"
:avatar-only="true"
:show-weapon="false"
:hide-class-badge="true"
:override-top-padding="'0px'"
:sprites-margin="'0 0 0 -30px'"
:debug-mode="false"
:center-avatar="true"
class="mb-3"
/>
<strong>{{ memberObj.profile.name }}</strong>
<div class="username mb-3">
@{{ memberObj.auth.local.username }}
</div>
<div
class="kind-text"
v-html="$t('rememberToBeKind')"
></div>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.centered {
align-content: center;
}
.center-avatar {
margin: 0 auto;
}
strong {
line-height: 1.71;
}
.username {
font-size: 12px;
line-height: 1.33;
color: $gray-100;
margin-top: -4px;
}
.kind-text {
width: 330px;
line-height: 1.71;
color: $gray-100;
}
</style>
<script>
import Avatar from '@/components/avatar.vue';
export default {
components: { Avatar },
props: {
memberObj: null,
},
};
</script>
@@ -1,44 +0,0 @@
export namespace PrivateMessages {
// Shared properties between message types
interface SharedMessageProps {
username: string;
contributor: Record<string, unknown>;
userStyles: Record<string, unknown>;
canReceive: boolean;
}
/**
* This is the Type we get from our API
*/
interface ConversationSummaryMessageEntry extends SharedMessageProps {
uuid: string;
user: string;
timestamp: string;
text: string;
count: number;
}
/**
* The Visual (Sidebar) Entry
*/
interface ConversationEntry extends SharedMessageProps {
/**
* UUID
*/
key: string;
name: string;
lastMessageText: '',
canLoadMore: boolean;
page: 0
}
/**
* Loaded Private Messages, partial type
*/
interface PrivateMessageEntry extends SharedMessageProps {
text: string;
}
}
@@ -1,165 +0,0 @@
<template>
<div class="ml-4">
<strong
v-once
v-html="$t('to')"
></strong>
<validated-text-input
id="selectUser"
ref="targetUserInput"
v-model="targetUserInputValue"
class="mx-2"
:is-valid="foundUser._id"
:only-show-invalid-state="foundUser._id === undefined"
:hide-error-line="true"
:placeholder="$t('usernameOrUserId')"
:invalid-issues="userInputInvalidIssues"
@enter="triggerNewConversation"
/>
<button
class="btn btn-primary"
:disabled="preventTrigger"
@click="triggerNewConversation()"
>
{{ $t('confirm') }}
</button>
<button
class="ml-2 btn btn-secondary"
@click="$emit('cancelNewConversation')"
>
{{ $t('cancel') }}
</button>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
div {
display: flex;
align-items: center;
}
div > * {
height: 32px;
}
strong {
line-height: 1.71;
align-content: center;
}
input {
border-radius: 2px;
border-width: 2px;
width: 420px;
}
#selectUser {
/* changing the style of validate-text-input to the same as others */
::v-deep {
.input-group {
border-width: 2px;
input {
width: 420px;
height: 100%;
color: $gray-50;
}
}
.input-group {
&:focus, &:active, &:focus-within {
border: solid 2px $purple-400;
}
}
}
}
</style>
<script>
import debounce from 'lodash/debounce';
import isUUID from 'validator/es/lib/isUUID';
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
export default {
components: {
ValidatedTextInput,
},
mixins: [],
data () {
return {
targetUserInputValue: '',
userNotFound: false,
foundUser: {},
};
},
computed: {
preventTrigger () {
return this.targetUserInputValue.length < 2;
},
userInputInvalidIssues () {
return this.targetUserInputValue.length > 0 && this.userNotFound
? [this.$t('userWithUsernameOrUserIdNotFound')]
: [''];
},
},
watch: {
targetUserInputValue: {
handler () {
this.searchUser(this.targetUserInputValue.replace('@', ''));
},
},
},
mounted () {
this.$refs.targetUserInput.focus();
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'select-user-modal');
},
searchUser: debounce(async function userSearch (searchTerm = '') {
this.foundUser = {};
if (searchTerm.length < 1) {
this.userNotFound = false;
return;
}
let result;
if (isUUID(searchTerm)) {
try {
result = await this.$store.dispatch('members:fetchMember', {
memberId: searchTerm,
});
} catch {
result = null;
}
} else {
try {
result = await this.$store.dispatch('members:fetchMemberByUsername', {
username: searchTerm,
});
} catch {
result = null;
}
}
if (!result) {
this.userNotFound = true;
return;
}
this.userNotFound = false;
this.foundUser = result;
}, 500),
triggerNewConversation () {
const userWithoutAt = this.$refs.targetUserInput.value.replace('@', '');
this.$emit('startNewConversation', userWithoutAt);
},
},
};
</script>
+3 -2
View File
@@ -49,7 +49,7 @@ const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ '@/com
const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/taskInformation');
const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/billing');
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages/index.vue');
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages');
// Challenges
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/index');
@@ -190,6 +190,7 @@ const router = new VueRouter({
meta: {
privilegeNeeded: [ // any one of these is enough to give access
'userSupport',
'newsPoster',
],
},
children: [
@@ -218,7 +219,7 @@ const router = new VueRouter({
// Only used to handle some redirects
// See router.beforeEach
{ path: '/static/tavern-and-guilds', redirect: '/static/faq/tavern-and-guilds' },
{ path: '/static/faq/tavern-and-guilds', redirect: '/static/tavern-and-guilds' },
{ path: '/redirect/:redirect', name: 'redirect' },
{ path: '*', redirect: { name: 'notFound' } },
],
+1 -8
View File
@@ -43,14 +43,7 @@ export async function deleteChat (store, payload) {
}
export async function like (store, payload) {
let url = '';
if (payload.groupId === 'privateMessage') {
url = `/api/v4/inbox/like-private-message/${payload.chatMessageId}`;
} else {
url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatMessageId}/like`;
}
const url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatId}/like`;
const response = await axios.post(url);
return response.data.data;
}
@@ -72,11 +72,7 @@ describe('LevelUp', () => {
it('generates the right test class for level 15', () => {
const questClass = testFunction('questClass', 15);
expect(questClass()).to.equal('inventory_quest_scroll_atom1');
});
it('generates empty test class for level 14', () => {
const questClass = testFunction('questClass', 14);
expect(questClass()).to.equal('');
expect(questClass()).to.equal('scroll inventory_quest_scroll_atom1');
});
});
@@ -1,16 +1,14 @@
import Vue from 'vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import BootstrapVue from 'bootstrap-vue';
import MessageCard from '@/components/messages/messageCard.vue';
import ChatCard from '@/components/chat/chatCard.vue';
import Store from '@/libs/store';
const localVue = createLocalVue();
localVue.use(Store);
localVue.use(Vue.directive('b-tooltip', {}));
localVue.use(BootstrapVue);
describe('MessageCard', () => {
describe('ChatCard', () => {
function createMessage (text) {
return { text, likes: {} };
}
@@ -28,7 +26,7 @@ describe('MessageCard', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(MessageCard, {
wrapper = shallowMount(ChatCard, {
propsData: { msg: message },
store: new Store({
state: {
-1
View File
@@ -43,7 +43,6 @@ envVars
});
const webpackPlugins = [
new webpack.ProvidePlugin({ 'window.jQuery': 'jquery' }),
new webpack.DefinePlugin(envObject),
new MomentLocalesPlugin({
localesToKeep: ['bg',
+1 -1
View File
@@ -1,5 +1,5 @@
{
"achievement": "Достижения",
"achievement": "Постижение",
"onwards": "Напред!",
"levelup": "Изпълнявайки целите си в истинския живот, Вие се качихте ниво и здравето Ви беше запълнено!",
"reachedLevel": "Достигнахте Ниво <%= level %>",
+1 -1
View File
@@ -91,7 +91,7 @@
"conText": "Якостта намалява щетите от отрицателни навици и пропуснати ежедневни задачи.",
"perception": "Усет",
"perText": "Усетът увеличава спечеленото злато, а след отключването на пазара увеличава вероятността за намиране на предмети след приключване на задачи.",
"intelligence": "Интелект",
"intelligence": "Интелигентност",
"intText": "Интелигентността увеличава спечеления опит, а след отключването на класовете определя максималната мана за използване за класовите умения.",
"levelBonus": "Бонус за ниво",
"allocatedPoints": "Разпределени точки",
+3
View File
@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Моля, помогнете на модераторите, като ни кажете защо докладвате тази публикация за нарушение, например: защото е нежелана, включва ругатни, клетви, фанатизъм, обиди, теми за възрастни, насилие.",
"optional": "Незадължително",
"needsTextPlaceholder": "Въведете съобщението си тук.",
"copyMessageAsToDo": "Копиране на съобщението като задача",
"copyAsTodo": "Копиране като задача за изпълнение",
"messageAddedAsToDo": "Съобщението беше копирано като задача.",
"leaderOnlyChallenges": "Само водачът на групата може да създава предизвикателства",
"sendGift": "Изпращане на подарък",
"inviteFriends": "Поканете приятели",
+3 -1
View File
@@ -46,8 +46,10 @@
"messageNotAbleToBuyInBulk": "Не може да се закупи повече от един брой от този предмет.",
"notificationsRequired": "Идентификаторите на известията са задължителни.",
"unallocatedStatsPoints": "Имате <span class=\"notification-bold-blue\"><%= points %> неразпределени показателни точки</span>",
"beginningOfConversation": "Това е началото на разговора Ви с <%= userName %>.",
"messageDeletedUser": "Съжаляваме, но този потребител е изтрил профила си.",
"messageMissingDisplayName": "Липсва екранно име.",
"canDeleteNow": "Вече може да изтриете съобщението, ако желаете.",
"reportedMessage": "Вие докладвахте това съобщние на модераторите."
"reportedMessage": "Вие докладвахте това съобщние на модераторите.",
"beginningOfConversationReminder": "Не забравяйте да бъдете мили, уважителни и да следвате Обществените Правила!"
}
+3 -6
View File
@@ -1,7 +1,7 @@
{
"quests": "Мисии",
"quest": "мисия",
"petQuests": "Мисии за домашни любимци и ездитни животни",
"petQuests": "Мисии за любимци и превози",
"unlockableQuests": "Мисии, които могат да бъдат отключени",
"goldQuests": "Последователности от мисии на класовите повелители",
"questDetails": "Подробности за мисията",
@@ -29,7 +29,7 @@
"collected": "Събрани",
"abort": "Прекратяване",
"leaveQuest": "Напускане на мисията",
"sureLeave": "Сигурни ли сте, че искате да се откажете от мисията? Ще загубите целия си напредък.",
"sureLeave": "Наистина ли искате да се откажете от текущата мисия? Ще загубите целия си напредък.",
"mustComplete": "Трябва първо да завършите <%= quest %>.",
"mustLvlQuest": "Трябва да бъдете ниво <%= level %>, за да купите тази мисия!",
"unlockByQuesting": "За да отключите тази мисия, първо завършете <%= title %>.",
@@ -78,8 +78,5 @@
"questAlreadyStartedFriendly": "Мисията вече е започнала, но винаги може да хванете следващата!",
"questAlreadyStarted": "Мисията вече е започнала.",
"questInvitationNotificationInfo": "Получихте покана за присъединяване към мисия",
"hatchingPotionQuests": "Мисии за Магическа Излюпваща Отвара",
"bossDamage": "Нанесохте вреда на главатаря!",
"questItemsPending": "<%= amount %> предмета ще бъдат събрани",
"sureLeaveInactive": "Сигурни ли сте, че искате да се откажете от мисията? Няма да можете да участвате в нея."
"hatchingPotionQuests": "Мисии за Магическа Излюпваща Отвара"
}
+2 -2
View File
@@ -58,11 +58,11 @@
"foundNewItemsCTA": "Podívej se do tvého Inventáře a zkus zkombinovat tvůj nový líhnoucí lektvar a vajíčko!",
"foundNewItemsExplanation": "Splnění úkolů ti dá šanci najít předměty jako vajíčka, líhnoucí lektvary a jídlo pro mazlíčky.",
"foundNewItems": "Nové předměty nalezeny!",
"hideAchievements": "Schovat <%= category %>",
"hideAchievements": "Schovat <%= kategorie %>",
"onboardingCompleteDesc": "Získáváš <strong>5 úspěchů</strong> a <strong class=\"gold-amount\">100 zlaťáků</strong> za dokončení seznamu.",
"onboardingProgress": "<%= percentage %>% postup",
"gettingStartedDesc": "Splň tyto základní úkoly a získej <strong>5 úspěchů</strong> a <strong class=\"gold-amount\">100 zlaťáků</strong>, jakmile budeš hotový/á!",
"showAllAchievements": "Zobrazit všechny <%= category %>",
"showAllAchievements": "Zobrazit všechny <%= kategorie %>",
"yourProgress": "Tvůj postup",
"achievementBareNecessitiesModalText": "Splnil/a jsi výpravy za opicí, lenochodem a stromečkem!",
"achievementBareNecessitiesText": "Splnil/a výpravy za opicí, lenochodem a stromečkem.",
+3
View File
@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Prosím pomož našim moderatorům a vysvětli, proč ohlašuješ tento příspěvek kvůli porušení pravidel, tedy zda je to spam, sprostá slova, náboženské přísahy, netolerance, urážky, témata nevhodná pro mladistvé, násilí.",
"optional": "Možný",
"needsTextPlaceholder": "Napiš svou zprávu sem.",
"copyMessageAsToDo": "Zkopírovat zprávu jako úkol",
"copyAsTodo": "Zkopírovat jako úkol",
"messageAddedAsToDo": "Zpráva zkopírována jako úkol.",
"leaderOnlyChallenges": "Pouze velitel družiny může vytvářet Výzvy",
"sendGift": "Poslat dárek",
"inviteFriends": "Pozvat přátele",
+2
View File
@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Tento předmět nelze nakoupit v množství větším, než je 1.",
"notificationsRequired": "Id upozornění je potřeba.",
"unallocatedStatsPoints": "Máš <span class=\"notification-bold-blue\"><%= points %> nepřidělený(ch) vlastnostní(ch) bod(ů)</span>",
"beginningOfConversation": "Toto je začátek tvé konverzace s uživatelem <%= userName %>.",
"messageDeletedUser": "Omlouváme se, ale tento uživatel smazal svůj účet.",
"messageMissingDisplayName": "Chybí zobrazované jméno.",
"canDeleteNow": "Nyní můžete zprávu smazat.",
"reportedMessage": "Tuto zprávu jste nahlásili moderátorům.",
"beginningOfConversationReminder": "Nezapomeňte být milí, taktní a respektujte Zásady komunity!",
"messageAllUnEquipped": "Vše odloženo.",
"messageBackgroundUnEquipped": "Pozadí odloženo.",
"messagePetMountUnEquipped": "Mazlíček a zvíře odloženi.",
+3
View File
@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Du kan hjælpe vores moderatorer ved at lade os vide, hvorfor du anmelder denne besked som en overtrædelse - fx spam, banden, religiøse kraftudtryk, fordomme, nedladende skældsord, emner for aldersgruppen +18 eller vold.",
"optional": "Valgfri",
"needsTextPlaceholder": "Skriv din besked her.",
"copyMessageAsToDo": "Kopier besked som To-Do",
"copyAsTodo": "Kopier som To-Do",
"messageAddedAsToDo": "Besked kopieret som To-Do.",
"leaderOnlyChallenges": "Kun gruppelederen kan oprette udfordringer",
"sendGift": "Send gave",
"inviteFriends": "Invitér venner",
+2
View File
@@ -46,11 +46,13 @@
"messageNotAbleToBuyInBulk": "Denne genstand kan ikke købes i antal større end 1.",
"notificationsRequired": "Notafikation ID'er er krævet.",
"unallocatedStatsPoints": "Du har <span class=\"notification-bold-blue\"><%= points %> ufordelte Egenskabspoint</span>",
"beginningOfConversation": "Dette er begyndelsen på din samtale med <%= userName %>.",
"messageDeletedUser": "Sorry, this user has deleted their account.",
"messageMissingDisplayName": "Missing display name.",
"newsPostNotFound": "News Post er ikke fundet eller du har ikke adgang.",
"canDeleteNow": "Du kan nu slette beskeden, hvis du ønsker det.",
"reportedMessage": "Du har indrapporteret denne besked til moderatorerne.",
"beginningOfConversationReminder": "Husk at være venlig, respektful og følge Retningslinjerne for Fællesskabet!",
"messageAllUnEquipped": "Alt fjernet.",
"messageBackgroundUnEquipped": "Baggrund fjernet.",
"messageCostumeUnEquipped": "Kostume fjernet.",
+1 -1
View File
@@ -152,7 +152,7 @@
"achievementDinosaurDynastyModalText": "Du hast alle Vogel- und Dinosaurier-Haustiere gesammelt!",
"achievementDinosaurDynasty": "Dinosaurier Dynastie",
"achievementBonelessBoss": "Knochenloser Boss",
"achievementBonelessBossText": "Hat alle wirbellosen Tiere ausgebrütet: Käfer, Schmetterling, Tintenfisch, Nacktschnecke, Oktopus, Schnecke und Spinne!",
"achievementBonelessBossText": "Hat alle wirbellosen Tiere ausgebrütet: Käfer, Schmetterling, Tintenfisch, Nacktschnecke, Oktopus, Schnecke und Spinnen!",
"achievementBonelessBossModalText": "Du hast alle wirbellosen Tiere gesammelt!",
"achievementDuneBuddyText": "Hat alle Standardfarben der Wüstenbewohnern ausgebrütet: Gürteltier, Kaktus, Fuchs, Frosch, Schlange und Spinne!",
"achievementRoughRider": "Harter Reiter",
+2 -14
View File
@@ -789,7 +789,7 @@
"backgroundBirthdayBashNotes": "Habitica feiert eine Geburtstagsparty und alle sind eingeladen!",
"eventBackgrounds": "Ereignis-Hintergründe",
"backgroundBirthdayBashText": "Geburtstagsparty",
"backgroundInsideACrystalNotes": "Schau aus dem Inneren eines Kristalls hinaus.",
"backgroundInsideACrystalNotes": "Schaue aus einem Kristall hinaus.",
"backgrounds072023": "SET 110: Veröffentlicht im Juli 2023",
"backgroundOnAPaddlewheelBoatText": "Auf einem Schaufelradboot",
"backgroundOnAPaddlewheelBoatNotes": "Fahre mit einem Schaufelradboot.",
@@ -885,17 +885,5 @@
"backgrounds102024": "Set 124: Veröffentlicht im September 2024",
"backgroundCastleHallWithHearthText": "Schlosshalle mit Feuerstelle",
"backgrounds112024": "SET 126: Veröffentlicht im November 2024",
"backgroundCastleHallWithHearthNotes": "Entspanne dich in der Wärme einer Schlosshalle mit einer Feuerstelle.",
"backgrounds122024": "SET 127: Veröffentlicht im Dezember 2024",
"backgroundFirstSnowForestText": "Der erste Schnee im Wald",
"backgroundFirstSnowForestNotes": "Tritt in den ersten Schnee im Wald.",
"backgrounds012025": "Set 128: Veröffentlicht im Januar 2025",
"backgroundWinterLandscapeWithCabinText": "Winterlandschaft mit Hütte",
"backgroundWinterLandscapeWithCabinNotes": "Macht es dir in einer Winterlandschaft mit einer Hütte gemütlich.",
"backgroundOldFashionedTeaShopText": "Altmodischer Teeladen",
"backgroundOldFashionedTeaShopNotes": "Genieße ein Getränk in einem Altmodischen Teeladen.",
"backgrounds022025": "Set 129: Veröffentlicht im Februar 2025",
"backgrounds032025": "SET 130: Veröffentlicht im März 2025",
"backgroundMountainSceneWithBlossomsText": "Bergszene mit Blüten",
"backgroundMountainSceneWithBlossomsNotes": "Erlebe den entzückenden Anblick und Geruch einer Bergszene mit Blüten."
"backgroundCastleHallWithHearthNotes": "Entspanne dich in der Wärme einer Schlosshalle mit einer Feuerstelle."
}
+1 -9
View File
@@ -391,13 +391,5 @@
"questEggRaccoonAdjective": "ein gefräßiger",
"questEggDogText": "Welpe",
"questEggDogMountText": "Hund",
"questEggDogAdjective": "ein freundlicher",
"hatchingPotionGingerbread": "Lebkuchen",
"questEggCatText": "Kätzchen",
"questEggCatMountText": "Katze",
"questEggCatAdjective": "ein schelmisches",
"questEggOtterText": "Otter",
"questEggOtterMountText": "Otter",
"hatchingPotionJade": "Jade",
"questEggOtterAdjective": "Ein perfider"
"questEggDogAdjective": "ein freundlicher"
}
+5 -7
View File
@@ -5,18 +5,18 @@
"webFaqStillNeedHelp": "Wenn Du eine Frage hast, die hier oder im [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ) nicht beantwortet wurde, verwende das Stell eine Frage Formular [LINK NEEDED]! Wir helfen Dir gerne.",
"parties": "Partys",
"webFaqAnswer25": "Habitica verwendet drei verschiedene Aufgabentypen, um deinen Bedürfnissen gerecht zu werden: Gewohnheiten, tägliche Aufgaben und To-Dos.\n\nGewohnheiten können positiv oder negativ sein und stellen etwas dar, das Sie vielleicht mehrmals am Tag oder nach einem nicht festgelegten Zeitplan verfolgen möchten. Positive Gewohnheiten bringen euch Belohnungen wie Gold und Erfahrung (Exp), während ihr bei negativen Gewohnheiten Lebenspunkte (HP) verliert.\n\nDailies sind wiederkehrende Aufgaben, die du nach einem strukturierten Zeitplan erledigen möchtest. Zum Beispiel einmal am Tag, dreimal in der Woche oder viermal im Monat. Wenn du Dailies verpasst, verlierst du HP, aber je schwieriger sie sind, desto besser ist die Belohnung!\n\nTo-Dos sind einmalige Aufgaben, für deren Erledigung es Belohnungen gibt. To-Dos können ein Fälligkeitsdatum haben, aber du verlierst keine HP, wenn du es verpasst.\n\nWähle die Aufgabenart, die am besten zu dem passt, was du erreichen willst!",
"commonQuestions": "Häufige Fragen",
"commonQuestions": "Häufige Fragenj",
"faqQuestion25": "Welche Aufgabentypen gibt es?",
"faqQuestion26": "Was sind einige Beispielaufgaben?",
"webFaqAnswer31": "Wenn du eine Aufgabe erfüllst und HP verlierst, obwohl du das nicht hättest tun sollen, kam es zu einer Verzögerung, während der Server die auf anderen Plattformen vorgenommenen Änderungen synchronisiert hat. Wenn du zum Beispiel Gold oder Mana verwendest oder HP in der mobilen App verlierst und dann eine Aufgabe auf der Website erledigst, bestätigt der Server lediglich, dass alles synchronisiert ist.",
"webFaqAnswer49": "Wenn Du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn Du bereits andere Spieler kennst, die eine Party haben, kannst Du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst Du auch eine neue Gruppe erstellen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die Dir am besten gefällt.",
"webFaqAnswer62": "Gruppenpläne bieten dir die einzigartige Möglichkeit, anderen Mitgliedern deines Gruppenplans gemeinsame Aufgaben zuzuweisen. Wenn eine gemeinsame Aufgabe einem Mitglied zugewiesen wird, können andere Mitglieder sie nicht mehr erledigen.\n\nDu kannst eine Aufgabe auch mehreren Mitgliedern zuweisen. Wenn sich zum Beispiel alle Mitglieder die Zähne putzen müssen, erstellst du eine Aufgabe und weist sie jedem Mitglied zu. Jedes Mitglied kann die Aufgabe erledigen und sich seine individuelle Belohnung verdienen. Die Hauptaufgabe wird als erledigt angezeigt, sobald alle Mitglieder sie erledigt haben.",
"webFaqAnswer32": "Alle Spieler beginnen in der Klasse des Kriegers, bis sie Stufe 10 erreicht haben. Sobald du Stufe 10 erreichst, hast du die Wahl, eine neue Klasse zu wählen oder als Krieger weiterzuspielen.\n\nJede Klasse verfügt über unterschiedliche Ausrüstungen und Fertigkeiten. Wenn du dich nicht für eine Klasse entscheiden möchtest, kannst du \"Abbrechen\" wählen. Wenn du dich später doch entscheidest, kannst du das Klassensystem in den Einstellungen wieder aktivieren.\n\nWenn Du Deine Klasse nach Level 10 noch einmal ändern möchtest, kannst Du die Sphäre der Wiedergeburt hierfür nutzen. Die Sphäre der Wiedergeburt ist mit Level 50 auf demMarktplatz für 6 Edelsteine verfügbar und mit Level 100 bekommst Du sie umsonst.\n\nAlternativ kannst Du Deine Klasse jederzeit in den Einstellungen für 3 Edelsteine ändern. Dies wird Dein Level nicht wie die Sphäre der Wiedergeburt zurücksetzen, aber es erlaubt Dir, die Fertigkeits-Punkte, die Du beim Leveln gesammelt hast, Deiner neuen Klasse zuzuordnen.",
"webFaqAnswer32": "In Habitica gibt es vier Klassen: Krieger, Magier, Schurke und Heiler. Alle Spieler beginnen in der Klasse des Kriegers, bis du Stufe 10 erreicht hast. Sobald du Stufe 10 erreichst, hast du die Wahl, eine neue Klasse zu wählen oder als Krieger weiterzuspielen.\n\nJede Klasse verfügt über unterschiedliche Ausrüstungen und Fertigkeiten. Wenn du dich nicht für eine Klasse entscheiden möchtest, kannst du \"Aussteigen\" wählen. Wenn du dich dafür entscheidest, kannst du das Klassensystem später in den Einstellungen wieder aktivieren.",
"sunsetFaqPara14": "<strong>Linguists</strong><br />Wir freuen uns auch weiterhin über Hilfe bei der Übersetzung der Apps und der Website und werden für qualifizierte Beiträge nach wie vor Beitragsstufen vergeben. Die Methode, mit der wir Übersetzungen annehmen, wird sich jedoch ändern. Wir möchten unsere Ressourcen auf die Unterstützung einer bestimmten Auswahl von Sprachen für alle Plattformen konzentrieren. Um dies zu erreichen, werden wir die Anzahl der für Übersetzungen verfügbaren Sprachen reduzieren. Zuvor nicht fertiggestellte Sprachen werden in Github archiviert. Wir hoffen, dass diese Änderung das plattformübergreifende Habitica-Erlebnis konsistenter macht. Sie können unsere aktuellsten Richtlinien für das Übersetzungsverfahren auf unserer Website lesen <a href='https://translate.habitica.com/projects/habitica/#information'>Übersetzungswebsite</a>.",
"webFaqAnswer34": "Haustiere mögen Futter, das zu ihrer Farbe passt. Basis-Tiere sind die Ausnahme, aber alle Basis-Tiere mögen den gleichen Gegenstand. Im Folgenden siehst du, welche Nahrungsmittel jedes Haustier mag:\n\n * Basistiere mögen Fleisch\n * Weiße Haustiere mögen Milch\n * Wüstenhaustiere mögen Kartoffeln\n * Rote Haustiere mögen Erdbeeren\n * Schattentiere mögen Schokolade\n * Skelett-Tiere mögen Fisch\n * Zombie-Tiere mögen verdorbenes Fleisch\n * Zuckerwatte rosa Haustiere mögen rosa Zuckerwatte\n * Zuckerwatte blaue Haustiere mögen blaue Zuckerwatte\n * Goldene Haustiere mögen Honig",
"webFaqAnswer35": "Sobald du dein Haustier genug gefüttert hast, um es zu einem Reittier zu machen, musst du diese Art von Haustier erneut ausbrüten, um es in deinem Stall zu haben.\n\nUm Reittiere in den mobilen Apps zu sehen:\n\n * Wähle im Menü \"Haustiere & Reittiere\" und wechseln zur Registerkarte \"Reittiere\".\n\nSo zeigst du Reittiere auf der Website an:\n\n * Wähle im Menü \"Inventar\" die Option \"Haustiere und Reittiere\" und scrollen nach unten zum Abschnitt \"Reittiere\"",
"webFaqAnswer37": "Kontrolliere, ob die Option Kostüm aktiviert ist. Wenn dein Avatar ein Kostüm trägt, wird dieses Ausrüstungsset anstelle deiner Kampfausrüstung angezeigt.\n\nSo schaltest du das Kostüm in den mobilen Apps ein:\n * Wähle im Menü \"Ausrüstung\", um den Schalter für das Kostüm zu finden.\n\nSo schaltest du das Kostüm auf der Website um:\n * Wähle in deinem Inventar \"Ausrüstung\" und suche den Schalter \"Kostüm\" auf der Registerkarte \"Kostüm\" in der Ausrüstungsschublade",
"webFaqAnswer41": "Mystische Sanduhren sind die exklusive Abonnentenwährung von Habitica, die im Zeitreisenden-Shop verwendet wird. Abonnenten erhalten 1 Mystische Sanduhr am Anfang jedes Monats, in dem sie Abonnenten-Vorteile haben, zusammen mit einigen weiteren Vorteilen. Bitte beachte unsere Abonnement-Optionen, wenn Du an den besonderen Hintergründen-, Haustier-, Quest- und Ausrüstungsangeboten im Zeitreisenden-Shop interessiert bist!",
"webFaqAnswer41": "Mystische Sanduhren sind die exklusive Abonnentenwährung von Habitica, die im Mysteriöse Zeitreisende Laden verwendet wird! Sanduhren werden nach einem bestimmten Zeitplan geliefert, der auf Ihrem Abonnementplan basiert.\n\nZeitplan für die Lieferung von Sanduhren:\n * 1-Monats-Abonnenten erhalten 1 Sanduhr am Anfang des Monats nach der 3. aufeinanderfolgenden Zahlung.\n * 3-Monats-Abonnenten erhalten 1 Sanduhr sofort nach Abschluss des Abonnements, dann 1 weitere Sanduhr zu Beginn des Monats nach jeder Erneuerung.\n * 6-Monats-Abonnenten erhalten 2 Sanduhren sofort nach Abschluss des Abonnements, dann 2 weitere Sanduhren zu Beginn des Monats nach jeder Verlängerung.\n * 12-Monats-Abonnenten erhalten 4 Sanduhren sofort nach Abschluss des Abonnements, dann 4 weitere Sanduhren zu Beginn des Monats nach jeder Verlängerung.",
"webFaqAnswer42": "Eine der besten Möglichkeiten, sich zu motivieren und sich selbst für die Erledigung von Aufgaben verantwortlich zu machen, ist der Beitritt zu einer Gruppe! Eine Party mit anderen Habitica-Spielern ist eine großartige Möglichkeit, Quests anzunehmen, um Haustiere und Ausrüstung zu erhalten, Stärkungszauber von den Fertigkeiten der Party-Mitglieder zu bekommen und deine Motivation zu steigern.\n\nEine weitere Möglichkeit, die Verantwortlichkeit zu erhöhen, ist die Teilnahme an einer Herausforderung. Herausforderungen fügen eurer Liste automatisch Aufgaben hinzu, die mit einem bestimmten Ziel verbunden sind! Außerdem bieten sie ein Element des Wettbewerbs mit anderen Habitica-Spielern, das dir einen Motivationsschub geben kann, während du nach dem Edelsteinpreis strebst. Es gibt offizielle Herausforderungen, die vom Habitica-Team erstellt wurden, sowie Herausforderungen, die von anderen Spielern erstellt wurden.",
"webFaqAnswer44": "Du musst die Challenge verlassen oder warten, bis die Challenge geschlossen wird, um die zugehörigen Aufgaben zu löschen. Ein rotes Megaphon-Symbol bedeutet, dass die Challenge geschlossen wurde, ein graues Megaphon bedeutet, dass die Challenge noch läuft.\n\nSo löschst du Challenge-Aufgaben in der **Android**-App:\n 1. Tippe auf eine Aufgabe, die zur Challenge gehört.\n 2. Tippe auf \"Löschen\" in der oberen rechten Ecke des Bildschirms.\n 3. Wähle, um die Aufgaben der Herausforderung aus deiner Aufgabenliste zu entfernen.\n\nSo löschst du Challenge-Aufgaben in der **iOS**-App:\n 1. Suche die Challenge-Aufgabe, die du löschen möchtest, und sieh dir das Megaphon-Symbol an.\n 2. Wenn das Megaphon-Symbol rot ist, tippe auf die Aufgabe und wähle unten \"Löschen\".\n 3. Wenn das Megaphon-Symbol grau ist, musst du die Herausforderung finden und sie verlassen.\n\nSo löschst du Challenge-Aufgaben auf der **Website**:\n 1. Suche die Challenge-Aufgabe, die du löschen möchtest, und sieh dir das Megaphon-Symbol an.\n 2. Wenn das Megaphon-Symbol rot ist, klicke darauf und wähle dann, die Aufgabe aus deiner Aufgabenliste zu entfernen.\n 3. Wenn das Megaphon-Symbol grau ist, musst du die Herausforderung finden und sie verlassen, um die Aufgabe zu entfernen.",
"sunsetFaqPara5": "Wenn Sie mehr über die Änderungen erfahren möchten, ließ bitte die folgenden Informationen.",
@@ -36,7 +36,7 @@
"faqQuestion30": "Was passiert, wenn ich keine HP mehr habe?",
"webFaqAnswer30": "Wenn deine HP Null erreichen, verlierst du eine Stufe, dein gesamtes Gold und ein Ausrüstungsstück, das du wieder kaufen kannst.",
"faqQuestion31": "Warum habe ich HP verloren, wenn ich mit einer nicht-negativen Aufgabe interagiere?",
"faqQuestion32": "Wie kann ich eine Klasse wählen?",
"faqQuestion32": "Wann kann ich eine Klasse wählen?",
"faqQuestion33": "Was ist der blaue Balken, der nach Level 10 erscheint?",
"webFaqAnswer33": "Nachdem du das Klassensystem freigeschaltet hast, schaltest du auch Fertigkeiten frei, die Mana benötigen, um genutzt werden zu können. Mana wird durch deinen INT-Wert bestimmt und kann durch Fertigkeiten und Ausrüstung angepasst werden.",
"faqQuestion34": "Welches Futter mag mein Haustier?",
@@ -241,7 +241,5 @@
"subscriptionDetail430": "Die Kündigung eines aktiven Abonnements wird ein Enddatum für deine Vorteile festsetzen, bis zu dem du vollen Zugang zu allen Abo-Vorteilen hast. Das bedeutet, dass du weiterhin am Start jedes Monats Mystische Sanduhren und Erhöhungen der Edelsteinobergrenze erhältst, solange du Zugang zu diesen Vorteilen hast.",
"subscriptionDetail440": "Am Tag, an dem diese Änderungen in Kraft treten, erhalten aktive Abonnenten mit einer ungeraden Anzahl an Edelsteinen pro Monat folgende Anpassungen ihrer Edelsteinobergrenze:",
"subscriptionDetail470": "Gruppenabonnentenvorteile verhalten sich genauso wie die eines wiederkehrenden 1-Monats-Abonnements. Du erhältst eine Mystische Sanduhr am Anfang jedes Monats und die Anzahl an Edelsteinen, die du jeden Monat auf dem Marktplatz kaufen kannst, wird sich erhöhen bis zu einem Limit von 50.",
"subscriptionPara3": "Wir hoffen, dass dieser neue Rhythmus besser vorhersagbar ist, mehr Zugang zur fantastischen Gegenstandauswahl im Laden des Zeitreisenden ermöglicht und noch mehr Motivation bietet, jeden Monat Fortschritte an deinen Aufgaben zu machen!",
"faqQuestion67": "Was sind die Klassen in Habitica?",
"webFaqAnswer67": "Klassen sind verschiedene Rollen, die dein Charakter spielen kann. Jede Klasse bietet ihre eigene Reihe von einzigartigen Vorteilen und Fähigkeiten beim Aufsteigen auf höhere Level. Diese Fähigkeiten können das Bearbeiten deiner Aufgaben ergänzen oder dabei helfen, deine Party beim Abschließen von Quests zu unterstützen.\n\nDeine Klasse bestimmt auch, welche Ausrüstung für dich in den Belohnungen, im Marktplatz und im Jahreszeitenmarkt zum Kauf erhältlich ist.\n\nHier ist eine Zusammenfassung jeder Klasse, um dir dabei zu helfen, diejenige zu wählen, welche am besten zu deinem Spielstil passt:\n#### **Krieger**\n* Krieger verursachen hohen Schaden bei Bossen und haben eine hohe Chance für kritische Treffer beim Abschließen von Aufgaben, was dich mit extra Erfahrung und Gold belohnt.\n* Stärke ist ihr primäres Attribut, welches den Schaden erhöht, den sie verursachen.\n* Ausdauer ist ihr sekundäres Attribut, welches den Schaden verringert, den sie erhalten.\n* Die Fähigkeiten der Krieger erhöhen die Ausdauer und Stärke der Party Kameraden.\n* Erwäge, einen Krieger zu spielen, wenn du es liebst, Bosse zu bekämpfen und auch ein wenig Schutz möchtest, wenn du gelegentlich Aufgaben versäumst.\n#### **Heiler**\n* Heiler haben eine starke Verteidigung und können sich selbst, sowie die Party Kameraden, heilen.\n* Ausdauer ist ihr primäres Attribut, welches ihre Heilungen verstärkt und den Schaden, den sie erhalten, verringert.\n* Intelligenz ist ihr sekundäres Attribut, welches ihr Mana und ihre Erfahrung erhöht.\n* Die Fähigkeiten der Heiler bewirken, dass ihre Aufgaben weniger rot werden und erhöhen die Ausdauer der Party Kameraden.\n* Erwäge, einen Heiler zu spielen, wenn du oft Aufgaben versäumst, und die Fähigkeit benötigst, dich selbst und deine Party Kameraden zu heilen. Heiler erreichen schnell neue Level.\n#### **Magier**\n* Magier gewinnen schnell neue Level und viel Mana, und verursachen Schaden bei Bossen in Quests.\n* Intelligenz ist ihr primäres Attribut, welches ihr Mana und ihre Erfahrung erhöht.\n* Wahrnehmung ist ihr sekundäres Attribut, welches ihr gefundenes Gold und ihre gefundenen Gegenstände vermehrt.\n* Die Fähigkeiten der Magier bewirken, dass ihre Aufgaben Strähnen eingefroren werden, stellen das Mana ihrer Party Kameraden wieder her, und erhöhen ihre Intelligenz.\n* Erwäge, einen Magier zu spielen, wenn du durch das schnelle Erreichen neuer Level und das Beisteuern von Schaden in Boss Quests motiviert wirst.\n#### **Schurke**\n* Schurken bekommen die meisten erbeuteten Gegenstände und das meiste Gold beim Erledigen von Aufgaben, und haben eine höhere Chance, kritische Treffer zu erzielen, was ihnen noch mehr Erfahrung und Gold beschert.\n* Wahrnehmung ist ihr primäres Attribut, welches ihr gefundenes Gold und ihre gefundenen Gegenstände vermehrt.\n* Stärke ist ihr sekundäres Attribut, welches den Schaden erhöht, den sie verursachen.\n* Die Fähigkeiten der Schurken helfen ihnen, versäumten Tagesaufgaben auszuweichen, Gold zu klauen, und die Wahrnehmung ihrer Party Kameraden zu erhöhen.\n* Erwäge, einen Schurken zu spielen, wenn du durch Belohnungen sehr motiviert wirst."
"subscriptionPara3": "Wir hoffen, dass dieser neue Rhythmus besser vorhersagbar ist, mehr Zugang zur fantastischen Gegenstandauswahl im Laden des Zeitreisenden ermöglicht und noch mehr Motivation bietet, jeden Monat Fortschritte an deinen Aufgaben zu machen!"
}
+1 -1
View File
@@ -170,7 +170,7 @@
"joinMany": "Schließe Dich über <%= userCountInMillions %> Millionen Leuten an und habe Spaß, während Du Deine Aufgaben erfüllst!",
"joinToday": "Tritt Habitica heute bei",
"signup": "Registrieren",
"getStarted": "Auf geht's",
"getStarted": "Auf gehts",
"mobileApps": "Mobile Apps",
"learnMore": "Mehr erfahren",
"communityInstagram": "Instagram",
+5 -323
View File
@@ -2800,12 +2800,12 @@
"armorMystery202406Text": "Phantom-Seeräuber Kleidung",
"headMystery202406Text": "Phantom-Seeräuber Hut",
"eyewearMystery202406Text": "Phantom-Seeräuber Maske",
"weaponArmoirePaintbrushNotes": "Ein Ruck purer Inspiration durchdringt dich, wenn du diesen Frabpinsel aufhebst, und ermöglicht dir, alles zu malen, was du dir vorstellen kannst. Erhöht Intelligenz um <%= int %>.Verzauberter Schrank: Malerset (Gegenstand 3 von 4).",
"weaponArmoirePaintbrushNotes": "Ein Ruck purer Inspiration durchdringt dich, wenn du diesen Frabpinsel aufhebst, und ermöglicht dir, alles zu malen, was du dir vorstellen kannst. Erhöht Intelligenz um <%= int %>.Verzauberter Schrank: Maler Set (Gegenstand 3 von 4).",
"weaponArmoirePaintbrushText": "Farbpinsel",
"weaponArmoireMopText": "Mopp",
"weaponArmoireCleaningClothText": "Putzlappen",
"weaponArmoireMopNotes": "Schritt 1: Tauche den Mopp in einen Eimer mit Wasser und Schaum. Schritt 2: Ziehe den Mopp über den Boden. Schritt 3: Tu so, als wäre das Ende des Mopp Stiels ein Mikrofon und singe mit voller Inbrunst. Schritt 4: Wiederhole Schritte 1-3, bis der Boden sauber ist. Erhöht Ausdauer und Wahrnehmung um jeweils <%= attrs %>. Reinigungs-Set Zwei (Gegenstand 2 von 3)",
"weaponArmoireCleaningClothNotes": "Nimm dieses Putzwerkzeug auf deine Abenteuer mit und sei immer bereit, eine hübsche Gedenktafel zu polieren oder eine hölzerne Fensterbank zu wischen. Erhöht Stärke und Ausdauer um jeweils <%= attrs %>. Verzauberter Schrank: Reinigungs-Set Zwei (Gegenstand 3 von 3)",
"weaponArmoireMopNotes": "Schritt 1: Tauche den Mopp in einen Eimer mit Wasser und Schaum. Schritt 2: Ziehe den Mopp über den Boden. Schritt 3: Tu so, als wäre das Ende des Mopp Stiels ein Mikrofon und singe mit voller Inbrunst. Schritt 4: Wiederhole Schritte 1-3, bis der Boden sauber ist. Erhöht Ausdauer und Wahrnehmung um jeweils <%= attrs %>. Putzausrüstungs-Set Zwei (Gegenstand 2 von 3)",
"weaponArmoireCleaningClothNotes": "Nimm dieses Putzwerkzeug auf deine Abenteuer mit und sei immer bereit, eine hübsche Gedenktafel zu polieren oder eine hölzerne Fensterbank zu wischen. Erhöht Stärke und Ausdauer um jeweils <%= attrs %>. Verzauberter Schrank: Putzausrüstung Set Zwei (Gegenstand 3 von 3)",
"weaponArmoireRidingBroomText": "Reitbesen",
"weaponArmoireRidingBroomNotes": "Reite auf diesem feinen Besen zu all deinen magischsten Besorgungen--oder nimm ihn für eine Spritztour durch die Nachbarschaft. Wuui! Erhöht Stärke um <%= str %> und Intelligenz um <%= int %>. Verzauberter Schrank: Spukhaftes Zauberer Set (Gegenstand 1 von 3)",
"weaponArmoireHattersShearsText": "Scharfe Scheren",
@@ -2924,7 +2924,7 @@
"armorSpecialFall2024HealerText": "Space Invader Rüstung",
"armorSpecialFall2024HealerNotes": "Sei eins mit der Galaxis und hypnotisiere Zuschauer mit dieser Rüstung. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2024 Herbstausrüstung.",
"armorSpecialFall2024MageText": "Unterwelt Hexer Rüstung",
"armorSpecialFall2024MageNotes": "Sei eins mit der Unterwelt und umarme die Macht der Magier, die vor dir diese Rüstung trugen. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2024 Herbstausrüstung.",
"armorSpecialFall2024MageNotes": "Sei eins mit der Unterwelt und umarme die Macht der Magier, die vor dir diese Rüstung trugen. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2024 Herbstaausrüstung.",
"weaponArmoireFunnyFoolBatonNotes": "Du kannst mit einem Schwung Deines Stabes eine Pointe vortragen, die Aufmerksamkeit erregen oder Beifall ernten. Erhöht Ausdauer und Stärke jeweils um <%= attrs %>. Verzauberter Schrank: Lustiges Narren-Set (Gegenstand 3 von 3)",
"armorArmoireTeaGownText": "Teeparty Kleid",
"armorArmoireTeaGownNotes": "Du bist zäh, kreativ, brilliant und so modisch! Erhöht Stärke und Intelligenz um jeweils <%= attrs %>. Verzauberter Schrank: Teeparty Set (Gegenstand 1 von 3).",
@@ -2969,323 +2969,5 @@
"armorArmoireBasketballUniformNotes": "Fragst du dich, was auf dem Rücken dieser Uniform aufgedruckt ist? Deine Glückszahl, natürlich! Erhöht Wahrnehmung um <%= per %>.Verzauberter Schrank: Altertümliches Basketballset (Gegenstand 1 von 2).",
"armorArmoireShawlCollarCoatNotes": "Ein weiser Zauberer sagte einst, dass nichts besser ist als es sowohl gemütlich zu haben als auch produktiv zu sein! Trage diesen warmen und stylischen Mantel, wenn du die diesjährigen Herausforderungen meisterst. Erhöht Ausdauer um <%= con %>.",
"armorArmoirePaintersApronText": "Schürze des Malers",
"armorArmoirePaintersApronNotes": "Diese Schürze kann deine Kleidung vor Farbe und deine kreativen Projekte vor harschen Kritiken schützen. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Malerset (Gegenstand 1 von 4).",
"weaponSpecialWinter2025WarriorText": "Axt des Elchkriegers",
"weaponSpecialWinter2025WarriorNotes": "Eine mächtige Axt für einen mächtigen Elch! Du wirst unaufhaltbar sein! Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"weaponSpecialWinter2025RogueText": "Schneeflockenausbruch",
"weaponSpecialWinter2025RogueNotes": "Stampfe und blende diese schwierigen Aufgaben zur Unterwerfung! Du wirst unaufhaltbar sein! Erhöht Stärke um <%= str %>.Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"weaponSpecialWinter2025HealerText": "Sternenzauberstab",
"weaponSpecialWinter2025HealerNotes": "Was du jetzt brauchst, sind mehr Lichter und ein leuchtender Stern obendrauf! Du wirst unaufhaltbar sein! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"weaponSpecialWinter2025MageText": "Nordlicht-Display",
"weaponSpecialWinter2025MageNotes": "Diese beeindruckende, farbenfrohe Show bietet die perfekte Kulisse! Du wirst unaufhaltbar sein! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"armorSpecialWinter2025WarriorText": "Rüstung des Elchkriegers",
"armorSpecialWinter2025RogueText": "Schneekostüm",
"armorSpecialWinter2025HealerText": "Lichterketten-Robe",
"armorSpecialWinter2025HealerNotes": "Funkle Deinen Weg durch deine Aufgaben. Sei nur vorsichtig - wenn eine Birne ausgeht, gehen alle aus. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"armorSpecialWinter2025MageText": "Aurora-Umhang",
"armorSpecialWinter2025MageNotes": "Wunder, Exzentrik, Verzauberung und Pracht werden Deine Tage füllen, wenn Du in diesem Umhang tanzt. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"armorSpecialWinter2025WarriorNotes": "Jeder wird beiseitetreten und Dir Platz machen, wenn Du diese Rüstung trägst. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"armorSpecialWinter2025RogueNotes": "Auch wenn Du so ausiehst, als wärst Du mit kaltem Schnee bedeckt, bist Du angenehm gewärmt, ausgelassen und glücklich, wenn du dieses Kostüm trägst. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2024-2025 Winterausrüstung.",
"armorMystery202412Text": "Zuckerstangen-Waldkaninchen-Umhang",
"armorMystery202412Notes": "Ein unterhaltsamer und fluffiger Look, um dich an einem Wintertag gemütlich zu halten. Gewährt keinen Attributbonus. Dezember 2024 Abonnentengegenstand.",
"armorArmoireDragonKnightsArmorText": "Drachenritterrüstung",
"armorArmoireFunnyFoolCostumeText": "Kostüm des lustigen Narren",
"armorArmoireStormKnightArmorText": "Rüstung des Sturmritters",
"armorArmoireCorsairsCoatAndCapeNotes": "Ob Du nun deine Zeit an den Docks totschlägst oder auf dem offenen Meer nach Gefahren Aussschau hältst, werden diese Dich mit Sicherheit trocken halten und Dich dramatisch aussehen lassen. Halte nur das Gleichgewicht an Deck. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Korsaren-Set (Gegenstand 1 von 3)",
"armorArmoireDragonKnightsArmorNotes": "Bündle die Stärke und Macht eines Drachen mit dieser Rüstung aus Silber und verlorenen Schuppen. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Drachenritter-Set (Gegenstand 2 von 3)",
"armorArmoireFunnyFoolCostumeNotes": "Dum-di-dum! Sicher scherzest Du. Dieses farbenfrohe Outfit steht Dir ausgezeichnet! Erhöht Stärke um <%= str %>. Verzauberter Schrank: Lustiger Narr-Set (Gegenstand 2 von 3)",
"armorArmoireStormKnightArmorNotes": "In dieser Rüstung bist Du fast unverwundbar. Deine Feinde werden niemals das Ende des Sturms sehen. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Sturmritter-Set (Gegenstand 2 von 3)",
"headSpecialWinter2023WarriorNotes": "Dieser Walrosshelm eignet sich hervorragend um mit einem Freund zu plaudern oder an einer pfiffigen Mahlzeit teilzunehmen. Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2022-2023 Winterausrüstung.",
"armorArmoireFestiveHelperOverallsText": "Festliche Helfer-Latzhosen",
"headSpecialWinter2023WarriorText": "Walrosshelm",
"armorArmoireFestiveHelperOverallsNotes": "Diese strapazierfähige und bequeme Latzhose eignet sich hervorragend um zu arbeiten, zu spielen und andere zu unterstützen. Außerdem hat sie Taschen! Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Festliches Helferset (Gegenstand 2 von 2)",
"eyewearMystery202308Text": "Schläfrige Augen",
"headSpecialWinter2023RogueText": "Geschenkschleife",
"headSpecialWinter2023RogueNotes": "Die Versuchung der Leute, dein Haar „auszupacken“, gibt dir Gelegenheit, das Ducken und Ausweichen zu üben. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2022-2023 Winterausrüstung.",
"headSpecialWinter2023MageText": "Feenlicht-Diadem",
"headSpecialSpring2023RogueText": "Raupen-Kutte",
"headSpecialWinter2023MageNotes": "Wurdest du mit einem Sternennacht-Elixier ausgebrütet? Denn ich habe für dich Sterne in meinen Augen. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2022-2023 Winterausrüstung.",
"headSpecialWinter2023HealerNotes": "Dieser kardinalrote Helm eignet sich perfekt, um die Wintersaison durch pfeifen und singen einzuläuten. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2022-2023 Winterausrüstung.",
"headSpecialWinter2023HealerText": "Kardinalroter Helm",
"headSpecialSpring2023RogueNotes": "Achte darauf, dass du diese verlockenden Fühler einziehst, wenn Vögel über dir jagen! Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2023 Frühlingsausrüstung.",
"headSpecialFall2024MageNotes": "Ob Du nun geheimnisvoll oder skurril bist, man wird Dich nicht übersehen, wenn Du dies trägst! Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Herbst-Ausrüstung 2024.",
"headSpecialFall2024MageText": "Zauberer-Maske der Unterwelt",
"shieldSpecialWinter2025HealerNotes": "Das perfekte Geschenk wartet nur darauf, geöffnet zu werden. Was könnte drin sein? Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Wintergegenstand 2024-2025.",
"shieldSpecialWinter2025HealerText": "Das Perfekte Geschenk",
"eyewearArmoireJewelersEyeLoupeNotes": "Diese Augenlupe vergrößert das zu bearbeitende Objekt, sodass Du jedes Detail sehen kannst. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Juwelierset (Gegenstand 1 von 2).",
"eyewearArmoireJewelersEyeLoupeText": "Juwelier-Augenlupe",
"headSpecialSpring2023WarriorText": "Kolibri Helm",
"headSpecialSpring2023MageNotes": "Du solltest diese Gläser in der Nacht tragen, damit du bei Mondlicht klar sehen kannst. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2023 Frühlingsausrüstung.",
"headSpecialSpring2023WarriorNotes": "Bedecke dein Gesicht mit irisierenden Federn, wenn du in die Schlacht fliegst. Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2023 Frühlingsausrüstung.",
"headSpecialSpring2023MageText": "Mondstein Visier",
"headSpecialSummer2023RogueText": "Guppy Kappe",
"headSpecialSummer2023WarriorText": "Goldfisch Flosse",
"headSpecialSummer2023WarriorNotes": "Diese fabelhafte Flosse gibt dir Stabilität, wenn du mühsamen, vor dir liegenden Aufgaben entgegenschwimmst. Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2023 Sommerausrüstung.",
"headSpecialSummer2023RogueNotes": "Gup, zwo, drei vier! Keine Zeit, gegessen zu werden, muß Aufgaben abhaken! Erhöht Wahrnehmung um <%= per %>.Limitierte Ausgabe 2023 Sommerausrüstung.",
"headSpecialSpring2023HealerText": "Lilien Blüte",
"headSpecialSummer2023MageText": "Korallen-Geweih",
"headSpecialSummer2023MageNotes": "Die Weisheit eines ganzen Ökosystems ist mit dir, wenn du deine maritime Magie wirkst. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2023 Sommerausrüstung.",
"headSpecialSummer2023HealerNotes": "Das sind keine Schlangen! Du kannst deine Augen öffnen, es ist sicher! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2023 Sommerausrüstung.",
"headSpecialSummer2023HealerText": "Seetang Krone",
"headSpecialFall2023RogueNotes": "Dieser verhexte Eintopf hat dir das haarige Gesicht und die langen Ohren eines Esels beschert! Wie überaus shakespeareisch. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2023 Herbstausrüstung.",
"headSpecialFall2023RogueText": "Verzaubertes Gesicht",
"headSpecialFall2023WarriorText": "Spukschirm",
"headSpecialFall2023WarriorNotes": "Welcher Horror lauert in diesem Reich der Verzerrung und des Rauschens? Du musst am Ball bleiben, um das herauszufinden! Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2023 Herbstausrüstung.",
"headSpecialFall2023MageText": "Scharlachrote Hexenmeister Maske",
"headSpecialFall2023MageNotes": "Mit stechenden Augen und pointiertem Flair macht es jede Illusion plötzlich zu einer Möglichkeit. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2023 Herbstausrüstung.",
"headSpecialWinter2024RogueText": "Schneeeulen Haube",
"headSpecialNye2023Notes": "Du hast einen Lächerlichen Partyhut erhalten! Trage ihn mit Stolz, wenn du das neue Jahr einläutest! Gewährt keinen Attributbonus.",
"headSpecialWinter2024RogueNotes": "Wen wirst du sehen, wenn du diese Hauuuube trägst? Nun, wen wirst du NICHT sehen? Du wirst jede Bewegung, jede Geste, jedes Detail um dich herum erfassen. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Winter 2023-2024 Ausrüstung.",
"headSpecialNye2023Text": "Lächerlicher Partyhut",
"headSpecialSpring2023HealerNotes": "Dieses brillante und farbenfrohe Bild hat die gleiche Farbgebung wie die Sphäre der Wiedergeburt! Wie symbolträchtig! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2023 Frühlingsausrüstung.",
"headSpecialFall2023HealerText": "Sumpfkreaturen-Maske",
"headSpecialFall2023HealerNotes": "Mit Augen, so dunkel wie das Moor, aus dem es entstieg, fixiert es seine Gegner. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2023 Herbstausrüstung.",
"headSpecialWinter2024WarriorNotes": "Noch nie war es so köstlich, seine Birne zu schützen! Erhöht Stärke um <%= str %>. Limitierte Ausgabe Winter 2023-2024 Ausrüstung.",
"headSpecialWinter2024HealerNotes": "Oh oh, ein missglückter Zauberspruch hat dich im Eis gefangen! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Winter 2023-2024 Ausrüstung.",
"headSpecialWinter2024HealerText": "Gefrorener Helm",
"headSpecialWinter2024MageNotes": "Die mit Fleece gefütterte Kapuze schützt dich vor Kälte, aber auch vor negativen Gedanken und Vibes. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Winter 2023-2024 Ausrüstung.",
"headSpecialSpring2024MageText": "Hibiskus-Hut",
"headSpecialWinter2024WarriorText": "Pfefferminz-Schokobruch-Helm",
"headSpecialSpring2024WarriorText": "Fluorit Kopfputz",
"headSpecialWinter2024MageText": "Narwal Zauberer Haube",
"headSpecialSpring2024MageNotes": "Was könnte einschüchternder auf Feinde wirken, als diesen mit Pollen bedeckten Hut zu zeigen? Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Frühling 2024 Ausrüstung.",
"headSpecialSpring2024RogueText": "Nass-Schnee-Kapuze",
"headSpecialSpring2024WarriorNotes": "Was könnte atemberaubender sein, als diese Kristallkrone im Kampf zu tragen? Erhöht Stärke um <%= str %>. Limitierte Ausgabe Frühling 2024 Ausrüstung.",
"headSpecialSpring2024HealerNotes": "Welche fröhlichen Lieder wirst du singen, wenn du diesen Helm trägst? Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Frühling 2024 Ausrüstung.",
"headSpecialSpring2024RogueNotes": "Welche Wünsche und Hoffnungen werden auftauchen, wenn Eis und Schnee einem fruchtbaren Boden weichen? Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Frühling 2024 Ausrüstung.",
"armorArmoireSnowyFluffTrimmedCoatText": "Schneeweißer Flauschmantel",
"armorArmoireSnowyFluffTrimmedCoatNotes": "Wenn die ersten Flocken um dich herum fallen, hält dich dieser Mantel nicht nur warm, sondern sorgt auch dafür, dass du dich perfekt in die verschneite Umgebung einfügst. Gleite mit Stil über das Eis! Erhöht Stärke und Intelligenz um jeweils <%= attrs %>. Verzauberter Schrank: Schneebedeckter Schlappergut-Set (Gegenstand 2 von 2).",
"headSpecialSummer2024HealerNotes": "Diese spiralförmige Muschel erinnert dich daran, nicht durchzudrehen. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Sommer 2024 Ausrüstung.",
"headMystery202301Text": "Tapfere Vulpinaohren",
"headSpecialSummer2024MageNotes": "Dieser Hut schwingt sanft in den Meeresströmungen und hilft dir, deine Weisheit zu kanalisieren. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Sommer 2024 Ausrüstung.",
"headSpecialWinter2025RogueNotes": "Dieser Hut hat definitiv etwas Magisches an sich, denn er verwandelt dich in einen Schneemenschen. Lass den Hasen nur nicht zu nahe an deine Karottennase herankommen. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Winterausrüstung 2024-2025.",
"headMystery202403Notes": "Du kannst dich glücklich schätzen, diese feine Mütze aus smaragdgrünem Samt mit ihrem feinen meergrünen Edelstein tragen zu können. Gewährt keinen Attributbonus. März 2024 Abonnentengegenstand.",
"headMystery202312Text": "Winterlich Blaues Haar",
"headSpecialSummer2024RogueText": "Nacktschnecken-Helm",
"headMystery202403Text": "Aquamarin-Glückskappe",
"headSpecialSummer2024WarriorText": "Walhai-Helm",
"headSpecialFall2024RogueNotes": "Ob du nun geschmeidig oder gerissen bist, wenn du das trägst, wirst du nicht übersehen! Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"headMystery202312Notes": "Diese ausgefallene Frisur erinnert an die frostigen Farben der Saison. Gewährt keinen Attributbonus. Dezember 2023 Abonnentengegenstand.",
"headSpecialSummer2024RogueNotes": "Dieser Helm mit seinen hornartigen Tentakeln hilft dir, dich zu verstecken, damit du zu deinen eigenen Bedingungen zuschlagen kannst. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Sommer 2024 Ausrüstung.",
"headSpecialSummer2024WarriorNotes": "Mit diesem hilfreichen Helm lassen sich knifflige Aufgaben in zwei Teile zerlegen. Erhöht Stärke um <%= str %>. Limitierte Ausgabe Sommer 2024 Ausrüstung.",
"headSpecialFall2024HealerNotes": "Ob du deinen Planeten verteidigst oder einen neuen erkundest, wenn du das trägst, wirst du nicht übersehen! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"headSpecialWinter2025WarriorText": "Elchkrieger-Helm",
"headSpecialWinter2025WarriorNotes": "Also, hör zu: Jetzt siehst du aus wie ein Elch. Trag dieses Geweih mit Stolz. Erhöht Stärke um <%= str %>. Limitierte Ausgabe Winterausrüstung 2024-2025.",
"headSpecialFall2024RogueText": "Schwarze Katzenmaske",
"headSpecialFall2024HealerText": "Space Invader-Maske",
"headSpecialWinter2025HealerNotes": "Es ist nicht nötig, sie zu entwirren, da sie bereits die Form eines Hutes haben. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Winterausrüstung 2024-2025.",
"headSpecialWinter2025MageText": "Aurorahut",
"headSpecialWinter2025MageNotes": "Dieser Hut ist mehr als nur ein schicker Fascinator, er lässt dich wie das Polarlicht selbst aussehen. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Winterausrüstung 2024-2025.",
"headSpecialSummer2024MageText": "Seeanemonen-Hut",
"headSpecialSummer2024HealerText": "Seeschneckenhaus",
"headSpecialFall2024WarriorText": "Feurige Koboldmaske",
"headSpecialFall2024WarriorNotes": "Egal, ob du schelmisch oder bedrohlich bist, wenn du das trägst, wirst du nicht übersehen! Erhöht Stärke um <%= str %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"headSpecialWinter2025RogueText": "Schneemaske",
"headSpecialWinter2025HealerText": "Lichterketten-Wirrwarr",
"headMystery202402Notes": "Diese hübsche rosa Mähne ist das perfekte Accessoire für den Februar und darüber hinaus. Gewährt keinen Attributbonus. Februar 2024 Abonnentengegenstand.",
"headMystery202402Text": "Paradiesisches Rosa Haar",
"headMystery202301Notes": "Dein Gehör wird so scharf sein, dass du das Hereinbrechen des Morgens und das Glitzern des Taus hören wirst. Gewährt keinen Attributbonus. Jänner 2023 Abonnentengegenstand.",
"headMystery202304Text": "Tiptop Teekannen-Deckel",
"headMystery202304Notes": "Dieser Helm gewährt dir Immuni-Tee-t. April 2023 Abonnentengegenstand.",
"headMystery202310Text": "Narrenkappe",
"headMystery202311Text": "Zauberweberhut",
"headMystery202311Notes": "Verwebe sogar Raum und Zeit mit deinem Willen. Gewährt keinen Attributbonus. November 2023 Abonnentengegenstand.",
"headMystery202310Notes": "Sie verbirgt dein Gesicht und verleiht deinen Augen dennoch einen beunruhigenden und gespenstischen Glanz. Gewährt keinen Attributbonus. Oktober 2023 Abonnentengegenstand.",
"headMystery202303Text": "Künstlermähnen-Haar",
"headMystery202303Notes": "Wie könnte man besser zeigen, dass man der Star dieser Geschichte ist, als mit blauem und unwahrscheinlich stacheligem Haar? Gewährt keinen Attributbonus. März 2023 Abonnentengegenstand.",
"headMystery202308Text": "Lila Protagonistenhaar",
"headMystery202308Notes": "Steht die widerspenstige Kutte, die aus der Mitte deines Kopfes ragt, für deine Hartnäckigkeit oder deinen Hang zum Unfug? Gewährt keinen Attributbonus. August 2023 Abonnentengegenstand.",
"headMystery202407Notes": "Mit diesen magischen Kiemen kannst du unter Wasser atmen! Gewährt keinen Attributbonus. Juli 2024 Abonnentengegenstand.",
"headMystery202411Notes": "Dieser Helm ist für deine Aufgaben ziemlich einschüchternd, wenn du dich kopfüber in die Arbeit stürzt! Gewährt keinen Attributbonus. November 2024 Abonnentengegenstand.",
"headMystery202501Notes": "Dieser glitzernde Hut erzeugt ständig ein leichtes und festliches Gewusel um dich herum. Gewährt keinen Attributbonus. Jänner 2025 Abonnentengegenstand.",
"headArmoireBeaniePropellerHatText": "Propeller-Beaniemütze",
"headMystery202409Text": "Sonnwend-Magierhut",
"headMystery202409Notes": "Die verzauberten Sonnenblumen auf diesem Hut sind mehr als nur eine fröhliche Dekoration, sie erfüllen den Träger mit mächtiger magischer Energie. Gewährt keinen Attributbonus. September 2024 Abonnentengegenstand.",
"headMystery202407Text": "Sympathische Axolotl-Haube",
"headMystery202411Text": "Borstenhelm",
"headMystery202412Text": "Zuckerstangen-Kaninchenhaube",
"headMystery202412Notes": "Warm und gemütlich, wie eine Tasse heißer Kakao mit Minze in einer Winternacht! Gewährt keinen Attributbonus. Dezember 2024 Abonnentengegenstand.",
"headMystery202501Text": "Frostbinder-Hut",
"headMystery202406Notes": "Die geisterhaften Federn, die diesen Hut zieren, leuchten schwach, wie die Wellen eines gespenstischen Meeres. Gewährt keinen Attributbonus. Juni 2024 Abonnentengegenstand.",
"headArmoireTeaHatText": "Teepartyhut",
"headArmoirePaintersBeretText": "Malermütze",
"headArmoireAdmiralsBicorneText": "Admirals-Zweispitz",
"headArmoireAdmiralsBicorneNotes": "Hut ab! Wenn du diesen Zweispitz trägst, wirst du weiser, klüger, mutiger... und größer sein. Erhöht Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Schrank: Admiralsset (Gegenstand 1 von 2).",
"headArmoireBeaniePropellerHatNotes": "Jetzt ist nicht die Zeit, um am Boden zu bleiben! Drehe diesen kleinen Propeller und erhebe dich so hoch, wie dein Ehrgeiz dich tragen wird. Erhöht alle Eigenschaften um <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
"headArmoirePaintersBeretNotes": "Mit dieser flotten Baskenmütze siehst du die Welt mit einem künstlerischen Auge. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Malerset (Gegenstand 2 von 4).",
"headArmoireTeaHatNotes": "Dieser elegante Hut ist so schick wie funktional. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Teepartyset (Gegenstand 2 von 3).",
"headArmoirePurpleSpookySorceryHatText": "Gespenstischer Lila Magierhut",
"headArmoirePurpleSpookySorceryHatNotes": "Purpurrot wie die Dämmerung und voller Geheimnisse, ist dieser Hut für all deine zauberhaften Bedürfnisse geeignet. Erhöht Wahrnehmung um <%= per %> und Ausdauer um <%= con %>. Verzauberter Schrank: Gespenstisches Magie-Set (Gegenstand 2 von 3).",
"headArmoireBlackSpookySorceryHatText": "Gespenstischer Schwarzer Magierhut",
"headArmoireBlackSpookySorceryHatNotes": "Schwarz wie die Nacht und voller Geheimnisse, ist dieser Hut für all deine zauberhaften Bedürfnisse geeignet. Erhöht Intelligenz um <%= per %> und Ausdauer um <%= con %>. Verzauberter Schrank: Gespenstisches Magie-Set (Gegenstand 3 von 3).",
"headArmoireDragonKnightsHelmText": "Drachenritterhelm",
"headArmoireCorsairsBandanaText": "Piratenbandana",
"headArmoireFunnyFoolCapText": "Lustige Narrenkappe",
"headArmoireWhiteFloppyHatNotes": "Viele Zaubersprüche wurden in diesen einfachen Hut eingenäht und verleihen ihm eine wundersame weiße Farbe. Erhöht Stärke, Intelligenz und Ausdauer um jeweils <%= attrs %>. Verzauberter Schrank: Weißes Loungewear -Set (Gegenstand 1 von 3).",
"headArmoireCorsairsBandanaNotes": "Egal, ob du deinen Kopf bedecken willst, falls eine Möwe über dich hinwegfliegt, oder ob du sicherstellen willst, dass deine Feinde dich nicht schwitzen sehen, dieses Tuch ist unverzichtbar. Füge einfach eine Zierperle für jedes Abenteuer hinzu, das du bestehst. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Korsaren-Set (Gegenstand 2 von 3)",
"headArmoireFunnyFoolCapNotes": "Die Glöckchen an diesem Hut könnten deine Gegner zum Kichern bringen, aber dir helfen sie nur, dich zu konzentrieren. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Lustiges Narren-Set (Gegenstand 1 von 3)",
"headArmoireDragonKnightsHelmNotes": "Mit den feurigen Elementen auf diesem Helm könnten dich Drachen für einen der ihren halten. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Drachenritter-Set (Gegenstand 1 von 3)",
"headArmoireStormKnightHelmText": "Sturmritterhelm",
"headArmoireGreenTrapperHatText": "Grüne Trappermütze",
"headArmoireGreenTrapperHatNotes": "Alle sagen, dass deine Mütze so warm aussieht! Und das ist sie tatsächlich. Achte nur darauf, dass du die Klappen von deinen Ohren ziehst, wenn die Leute mit dir reden, sonst hört sich das Ganze eher nach „dne ütze sht ss wrrm ss!“ an. Erhöht Ausdauer und Wahrnehmung um jeweils <%= attrs %> . Verzauberter Schrank: Trappermützen-Set (Gegenstand 1 von 2).",
"headArmoireStormKnightHelmNotes": "Nutze die Blitze dieses Geweihs, wenn du die Burg stürmst. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Sturmritter-Set (Gegenstand 1 von 3)",
"shieldSpecialWinter2024HealerText": "Salzstreuer",
"shieldMystery202408Text": "Geheimnisvolles Glitzern",
"shieldArmoireTrustyPencilText": "Treuer Bleistift",
"shieldSpecialWinter2024WarriorText": "Keksschild",
"shieldSpecialFall2024WarriorText": "Flammenschild",
"shieldArmoireSaucepanText": "Kochtopf",
"shieldArmoireBuoyantBeachBallText": "Strandball",
"shieldSpecialSummer2023WarriorText": "Goldfischseele",
"shieldSpecialFall2024HealerText": "Weltraumschild",
"shieldMystery202409Text": "Sonnwendmagierstab",
"shieldArmoireSafetyFlashlightText": "Sicherheitstaschenlampe",
"headArmoireFestiveHelperHatText": "Festlicher Helferhut",
"headArmoireSnowyTrapperHatText": "Verschneiter Trapperhut",
"headArmoireSnowyTrapperHatNotes": "Blaue, erfrorene Ohren gehören der Vergangenheit an. Freue Dich auf kuschelige Wärme mit Stil! Erhöht Ausdauer und Wahrnehmung um jeweils <%= attrs %> . Verzauberter Schrank: Verschneiter Trapperhut-Set (Gegenstand 1 von 2).",
"shieldSpecialSummer2024WarriorText": "Walhaiflosse",
"shieldSpecialSummer2024HealerText": "Meeresschneckenschild",
"shieldSpecialFallRogue2024Text": "Bänderstab",
"shieldSpecialWinter2025WarriorText": "Elchkriegerschild",
"shieldMystery202501Text": "Frostbinderstab",
"shieldSpecialWinter2023WarriorText": "Austernschild",
"shieldSpecialWinter2023WarriorNotes": "„Die Zeit ist reif“, das Walroß sprach, „Von mancherlei zu reden Von Austernschal'n und Schneegeläut Von Liedern die da schweben. Und wohin die Perle des Schilds verschwunden ist oder wie wir im neuen Jahr leben. Erhöht Ausdauer um <%= con %>.Limitierte Ausgabe 2022-2023 Winterausrüstung.",
"shieldSpecialSpring2023WarriorText": "Blumenstrauß",
"shieldSpecialSummer2023HealerText": "Seeigel",
"shieldArmoireTeaKettleText": "Teekessel",
"shieldSpecialFall2023RogueText": "Verhexte Flasche",
"shieldSpecialFall2023WarriorText": "Bequemes Kissen",
"shieldSpecialFall2023HealerText": "Moosiger Felsen",
"shieldArmoireBucketText": "Eimer",
"shieldArmoireBasketballText": "Basketball",
"backMystery202301Text": "Fünf Schweife der Tapferkeit",
"shieldArmoirePaintersPaletteText": "Malerpalette",
"shieldSpecialSpring2023HealerText": "Lilienmieder",
"shieldSpecialWinter2023HealerNotes": "Dein Lied von Frost und Schnee besänftigt die Geister aller, die es hören. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2022-2023 Winterausrüstung.",
"shieldMystery202409Notes": "Der leuchtende Rubin auf diesem Stab bezieht seine Kraft aus der Spätsommersonne. Gewährt keinen Attributbonus. September 2024 Abonnentengegenstand.",
"backMystery202410Text": "Kandiszucker-Schweif",
"headAccessoryMystery202410Text": "Kandiszucker-Ohren",
"headAccessoryMystery202410Notes": "Sind das die Geräusche von „Süßes-oder-Saures“-Kindern an deiner Tür? Gewährt keinen Attributbonus. Oktober 2024 Abonnentengegenstand.",
"bodyMystery202411Text": "Stachelige Schulterplatten",
"headArmoireFestiveHelperHatNotes": "Urlaubstipp Nr. 27: Halten Sie einen Helferhut bereit. Dieser ist groß genug, um ein Notfallspielzeug darunter zu verstecken! Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Festliches Helferset (Gegenstand 1 von 2)",
"shieldMystery202501Notes": "Dekoriere jede Außenumgebung mit einer diamantenen Schicht aus schimmerndem Frost. Gewährt keinen Attributbonus. Jänner 2025 Abonnentengegenstand.",
"shieldSpecialWinter2025WarriorNotes": "Blocke alle unerwünschten Ablenkungen mit diesem Schild, das so stark wie ein Elch ist. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Winterausrüstung 2024-2025.",
"backMystery202410Notes": "Dieser Schweif wird bei der Erwähnung von gruseligen Leckereien aktiv. Gewährt keinen Attributbonus. Oktober 2024 Abonnentengegenstand.",
"shieldArmoireSafetyFlashlightNotes": "Warte, hast du das Geräusch gehört? Schnell! Leuchte mit deiner Taschenlampe in den Schatten dort drüben. Hmmm. Sieht aus, als wäre es nur der Wind gewesen. Oder war es...? Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Gruselnacht-Set (Gegenstand 1 von 2)",
"bodyMystery202411Notes": "Die furchterregenden Stacheln auf diesen Schulterplatten sind perfekt, um deine Aufgabenliste in Angriff zu nehmen. Gewährt keinen Attributbonus. November 2024 Abonnentengegenstand.",
"shieldSpecialSpring2023WarriorNotes": "Sammle die schönsten Blumen des Frühlings in diesem farbenfrohen Blumenstrauß. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2023 Frühlingsausrüstung.",
"shieldSpecialWinter2023HealerText": "Coole Lieder",
"shieldSpecialSpring2023HealerNotes": "Ein Beitrag zu einem Genesungsbesuch oder Teil eines Rituals für einen Frühlingstanz! Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2023 Frühlingsausrüstung.",
"shieldSpecialWinter2024WarriorNotes": "Du bist ein tougher Keks, der niemals bröselt! Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Winter 2023-2024 Ausrüstung.",
"shieldSpecialSummer2023HealerNotes": "Du verbirgst und beschützt es. Es hält neugierige Monster davon ab, zu nahe zu kommen. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2023 Sommerausrüstung.",
"shieldSpecialFall2023RogueNotes": "Mit den stärksten Zaubern verstärkt, um mächtige Tränke zu halten. Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2023 Herbstausrüstung.",
"shieldSpecialFall2023WarriorNotes": "Perfekt, um es dir bequem zu machen, während du einen Horrorfilm genießt... Aber wir verraten es niemand, wenn du es bei den grusligen Szenen in den Arm nehmen musst! Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2023 Herbstausrüstung.",
"shieldSpecialSummer2023WarriorNotes": "Beschwöre diesen Goldfischgeist für einen extra Schub Bestärkung und Begleitung während eines Kampfes. Ausdauer um <%= con %>. Limitierte Ausgabe 2023 Sommerausrüstung.",
"shieldSpecialWinter2024HealerNotes": "Wie praktisch, dass du beim Einfrieren eisschmelzende Materialien dabei hattest! Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Winter 2023-2024 Ausrüstung.",
"shieldSpecialSummer2024WarriorNotes": "Zu denen, die behaupten, du könntest deine Ziele nicht erreichen, sag einfach: Sprich zu meiner Hand, äh, Flosse! Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Sommer 2024 Ausrüstung.",
"shieldSpecialSummer2024HealerNotes": "Dieses glänzende Schild ist sogar stärker als ein Meeresschneckenstab. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Sommer 2024 Ausrüstung.",
"shieldSpecialFall2023HealerNotes": "Mit seinem festen Kern und dem weichen Bezug ist er ideal, um ihn auf Feinde zu schleudern oder um sich darauf zu setzen, wenn man eine Pause von seinen Abenteuern braucht. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2023 Herbstausrüstung.",
"shieldSpecialFall2024HealerNotes": "Neue Aufgaben, die nach deiner Aufmerksamkeit greifen, prallen ab, bis du deine aktuelle Mission erledigt hast. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"shieldSpecialFall2024WarriorNotes": "Komplikationen bei Aufgaben werden von deinem Schild absorbiert und machen dich entschlossener. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"shieldSpecialFallRogue2024Notes": "Die Aufgaben selbst werden von den Wirbeln und Spiralen dieser hypnotischen Waffe überwältigt. Erhöht Stärke um <%= str %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"headAccessoryMystery202212Notes": "Mit dieser verschnörkelten goldenen Tiara werden deine Herzlichkeit und Freundschaft neue Höhen erreichen. Gewährt keinen Attributbonus. Dezember 2022 Abonnentengegenstand.",
"headAccessoryMystery202212Text": "Eis-Tiara",
"shieldMystery202408Notes": "Die magischen Lichter beleuchten das Innere deines Seifenblasenverstecks oder jeden anderen Ort, an dem du ein wenig Licht brauchst! Gewährt keinen Attributbonus. August 2024 Abonnentengegenstand.",
"shieldArmoireJewelersPliersText": "Juwelierzange",
"shieldArmoireJewelersPliersNotes": "Sie schneidet, biegt, presst und vieles mehr. Mit diesem Werkzeug kannst du alles machen, was du dir vorstellen kannst. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Juwelierset (Gegenstand 3 von 4).",
"shieldArmoireTeaKettleNotes": "In diesem Kessel kannst du all deine geschmackvollen Lieblingstees aufbrühen. Hast du Lust auf schwarzen Tee, grünen Tee, Oolong oder vielleicht einen Kräutertee? Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Teekränzchen Set (Gegenstand 3 von 3).",
"shieldArmoireBasketballNotes": "Zisch! Wann immer du diesen magischen Basketball abschießt, wird es nichts als Treffer geben. Erhöht Ausdauer und Stärke um jeweils <%= attrs %> . Verzauberter Schrank: Altmodisches Basketballset (Gegenstand 2 von 2).",
"shieldArmoirePaintersPaletteNotes": "Farben in allen Facetten des Regenbogens stehen dir zur Verfügung. Ist es Magie, die sie so lebendig macht, wenn du sie benutzt, oder ist es dein Talent? Erhöht Stärke um <%= str %>. Verzauberter Schrank: Malerset (Gegenstand 4 von 4).",
"shieldArmoireBucketNotes": "Obwohl dieser Eimer für eine Mischung aus Wasser und Reinigungslösung gedacht ist, kannst du ihn auch zum Sammeln, Tragen und Transportieren von allem verwenden, was hineinpasst! Erhöht Stärke und Intelligenz um jeweils <%= attrs %>. Verzauberter Schrank: Reinigungs-Set 2 (Gegenstand 1 von 3)",
"backMystery202402Text": "Paradiesische Pinke Herzen",
"shieldArmoireSaucepanNotes": "Schau in diesen dampfenden Kochtopf und finde die Antwort auf das bestgehütete Geheimnis des Lebens! (Suppe. Die Antwort ist immer Suppe.) ErhöhtWahrnehmung um <%= per %>. Verzauberter Schrank: Küchenwerkzeugset 2 (Gegenstand 1 von 2).",
"shieldArmoireBuoyantBeachBallNotes": "Hast du schon zu viele Bälle in der Luft? Hier ist einer, den du sicher absetzen, rollen, hüpfen und hüpfen und hüpfen lassen kannst... Erhöht Stärke um <%= str %>. Verzauberter Schrank: Strand-Set (Gegenstand 4 von 4).",
"shieldArmoireTrustyPencilNotes": "Du weißt, was man sagt: Der Bleistift ist mächtiger als der Schwertstift. Moment... das klingt nicht ganz richtig... Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Schuluniformset (Gegenstand 4 von 4).",
"backMystery202401Notes": "Beschwöre sanftes Schneegestöber herauf oder rufe einen mächtigen Schneesturm herbei. Du hast die Wahl! Gewährt keinen Attributbonus. Jänner 2024 Abonnentengegenstand.",
"backMystery202402Notes": "Lass dich von einer Aura liebevoller Energie umgeben, wohin du auch gehst! Gewährt keinen Attributbonus. Februar 2024 Abonnentengegenstand.",
"backMystery202302Text": "Betrügerischer Tabby-Schweif",
"backMystery202301Notes": "Diese flauschigen Schweife enthalten ätherische Kräfte und auch ein hohes Maß an Charme! Gewährt keinen Attributbonus. Jänner 2023 Abonnentengegenstand.",
"backMystery202302Notes": "Wann immer du diesen Schweif trägst, wird es ein toller Tag werden! Callooh! Callay! Gewährt keinen Attributbonus. Februar 2023 Abonnentengegenstand.",
"backMystery202305Text": "Abendliche Flügel",
"backMystery202305Notes": "Fang das Funkeln des Abendsterns ein und schwebe auf diesen Flügeln in fremde Gefilde. Gewährt keinen Attributbonus. Mai 2023 Abonnentengegenstand.",
"backMystery202309Text": "Kolossale Kometenmottenflügel",
"backMystery202309Notes": "Flattere durch Wälder, gleite über Berge und schwebe über Ozeane auf diesen hellen und schönen Flügeln. Gewährt keinen Attributbonus. September 2023 Abonnentengegenstand.",
"backSpecialAnniversaryText": "Habitica Helden Cape",
"backSpecialAnniversaryNotes": "Lass dieses stolze Cape im Wind flattern und erzähle jedem, dass du ein Habitica Held bist. Gewährt keinen Attributbonus. Gegenstands-Sonderausgabe zur 10. Geburtstagsfeier.",
"backSpecialHeroicAureoleText": "Heroische Aureole",
"backSpecialHeroicAureoleNotes": "Die Edelsteine auf dieser Aureole schimmern, wenn du deine ruhmvollen Geschichten erzählst. Erhöht alle Eigenschaften um <%= attrs %>.",
"bodySpecialAnniversaryText": "Habiticas Heldenkragen",
"bodySpecialAnniversaryNotes": "Ergänzt dein königspurpurnes Kostüm perfekt! Gewährt keinen Attributbonus. Sonderausgaben-Gegenstand zur 10. Geburtstagsfeier.",
"eyewearMystery202312Text": "Winterliche blaue Augen",
"bodyArmoireKarateBrownBeltNotes": "Dieser Gürtel ist für diejenigen, deren Techniken und Fähigkeiten ausgereift sind. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Karateset (Gegenstand 9 von 10).",
"bodyArmoireKarateBrownBeltText": "Brauner Gürtel",
"headAccessoryMystery202302Text": "Trickbetrüger Tabby-Ohren",
"headAccessoryMystery202307Text": "Krakenkrone",
"bodyArmoireKarateOrangeBeltNotes": "Dieser Gürtel ist für diejenigen, die sich gesteigert und das Einsteigerlevel gemeistert haben. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Karateset (Gegenstand 4 von 10).",
"bodyArmoireKarateGreenBeltText": "Grüner Gürtel",
"bodyArmoireKarateBlackBeltText": "Schwarzer Gürtel",
"bodyArmoireKarateYellowBeltNotes": "Dieser Gürtel ist für Einsteiger, welche die Grundlagen gelernt haben. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Karateset (Gegenstand 3 von 10).",
"eyewearMystery202312Notes": "Kein Grund zur Sorge, diese eisigen Blautöne helfen dir, hinter der kalten und dunklen Jahreszeit die Wärme der nachfolgenden Monate zu erspähen. Gewährt keinen Attributbonus. Dezemeber 2023 Abonnentengegenstand.",
"eyewearMystery202406Notes": "Versuch zu vermeiden, dass dies von einer Bande aufdringlicher Kinder und ihrem sprechenden Hund abgezogen wird. Gewährt keinen Attributbonus. Juni 2024 Abonnentengegenstand.",
"bodyArmoireKarateOrangeBeltText": "Orangener Gürtel",
"headAccessoryMystery202305Text": "Abendzeitliche Hörner",
"eyewearMystery202303Notes": "Vermittle deinen Feinden durch deinen lässigen Gesichtsausdruck ein falsches Gefühl der Sicherheit. Gewährt keinen Attributbonus. März 2023 Abonnentengegenstand.",
"eyewearMystery202308Notes": "Bist du schläfrig oder ruhst du deine Augen nur in Erwartung deines nächsten tollen Kampfes aus? Gewährt keinen Attributbonus. August 2023 Abonnentengegenstand.",
"bodyArmoireKarateWhiteBeltText": "Weißer Gürtel",
"bodyArmoireKarateWhiteBeltNotes": "Dieser niedrigste Gürtel ist für jene, die ihre Reise gerade erst beginnen. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Karateset (Gegenstand 2 von 10).",
"bodyArmoireKarateGreenBeltNotes": "Dieser Gürtel ist für diejenigen gedacht, die auf fortgeschrittenem Niveau lernen, ihre Fähigkeiten zu verbessern. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Karateset (Gegenstand 5 von 10).",
"bodyArmoireKarateBlueBeltNotes": "Dieser Gürtel ist für diejenigen, die mehr lernen und ihren Geist und Körper entwickeln wollen. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Karateset (Gegenstand 6 von 10).",
"headAccessoryMystery202302Notes": "Das schnurrige Accessoire, das dein bezauberndes Grinsen unterstreicht. Gewährt keinen Attributbonus. Februar 2023 Abonnentengegenstand.",
"headAccessoryMystery202307Notes": "Dieser mächtige Stirnreif beschwört Wirbelstürme und stürmisches Wetter herauf! Gewährt keinen Attributbonus. Juli 2023 Abonnentengegenstand.",
"headAccessoryMystery202305Notes": "Diese Hörner leuchten durch das reflektierte Mondlicht. Gewährt keinen Attributbonus. Mai 2023 Abonnentengegenstand.",
"bodyArmoireKarateYellowBeltText": "Gelber Gürtel",
"bodyArmoireKaratePurpleBeltNotes": "Dieser Gürtel ist für diejenigen, die für anspruchsvolle Übungen bereit sind. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Karateset (Gegenstand 7 von 10).",
"bodyArmoireKarateRedBeltText": "Roter Gürtel",
"bodyArmoireKarateRedBeltNotes": "Dieser Gürtel ist für diejenigen, die gelernt haben, bei ihren Übungen vorsichtig vorzugehen. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Karateset (Gegenstand 8 von 10).",
"bodyArmoireKarateBlackBeltNotes": "Dieser höchste Gürtelgrad ist für diejenigen, die ein tieferes Verständnis anstreben und ihr Wissen an andere weitergeben dürfen. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Karateset (Gegenstand 10 von 10).",
"headAccessorySpecialHeroicCircletText": "Heldenhafter Stirnreif",
"headAccessorySpecialHeroicCircletNotes": "Schwer ist das Haupt, das die Krone trägt, aber dieser Stirnreif ist so leicht wie dein großzügiger Geist. Erhöht alle Werte um <%= attrs %>.",
"headAccessoryMystery202309Text": "Kolossale Kometenmotten-Antennen",
"headAccessoryMystery202309Notes": "Diese Antennen sind modisch und gefiedert, helfen aber auch bei der Navigation! Gewährt keinen Attributbonus. September 2023 Abonnentengegenstand.",
"headAccessoryMystery202310Text": "Geisterlicht-Krone",
"headAccessoryMystery202310Notes": "Wie ein Irrlicht können diese unheimlichen Lichter neugierige Seelen in ihr Verderben locken. Gewährt keinen Attributbonus. Oktober 2023 Abonnentengegenstand.",
"eyewearSpecialAnniversaryNotes": "Schau durch die Augen eines Habitica-Helden - durch deine! Gewährt keinen Attributbonus. Sonderausgaben-Gegenstand zur 10. Geburtstagsfeier.",
"bodyArmoireKarateBlueBeltText": "Blauer Gürtel",
"bodyArmoireKaratePurpleBeltText": "Violetter Gürtel",
"eyewearMystery202303Text": "Verträumte Augen",
"eyewearSpecialAnniversaryText": "Habiticas Heldenmaske",
"shieldArmoireFancyFloralFanNotes": "Beende deinen bezaubernden Look mit diesem erstklassigen Fächer aus besonders blumiger Baumwolle. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Bezauberndes Blumenset (Gegenstand 2 von 2).",
"armorMystery202502Notes": "Du bist voller gutmütiger Witze und Scherze, von deinem zerrupften Kragen bis zu deinen gigantischen Schuhen! Gewährt keinen Attributbonus. Februar 2025 Abonnentengegenstand.",
"armorMystery202502Text": "Herzvoller Harlekin Anzug",
"headMystery202502Text": "Herzvoller Harlekin Hut",
"headMystery202502Notes": "Dieses fröhliche Hütchen wird bei jedem, der dich sieht, für Freude sorgen! Gewährt keinen Attributbonus. Februar 2025 Abonnentengegenstand.",
"headArmoireFancyFloralHatText": "Bezaubernder Blumenhut",
"shieldMystery202502Text": "Herzvolle Harlekin Ballons",
"shieldMystery202502Notes": "Möge dein Herz an diesen Valentinstag, und auch jeden anderen Tag, so leicht sein wie diese beschwingten Luftballons. Gewährt keinen Attributbonus. Februar 2025 Abonnentengegenstand.",
"shieldArmoireFancyFloralFanText": "Bezaubernder Blumenfächer",
"headArmoireFancyFloralHatNotes": "Bestaune diesen bezaubernden Hut voller hinreißender Blumen und verschnörkelten Schnallen. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Bezauberndes Blumen Zubehör Set (Gegenstand 1 von 2).",
"weaponSpecialSpring2025WarriorNotes": "Mit einem Schnitt kannst du durch Blumenstängel säbeln, um ein Bukett zu machen, oder direkt durch Hindernisse, um deine Aufgaben zu erfüllen. Erhöht Stärke um <%= str %>. Limitierte Ausgabe Frühlingsausrüstung 2025.",
"weaponSpecialSpring2025WarriorText": "Sonnenschein-Säbel",
"weaponSpecialSpring2025RogueText": "Kristallspitzen-Morgenstern",
"weaponSpecialSpring2025RogueNotes": "Mit einem Schwinger kannst du jedes Hindernis eliminieren, das deinen Zielen im Weg steht. Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"weaponSpecialSpring2025MageNotes": "Mit einem Schwinger kannst du Elementarmagie nutzen, um deine Umgebung zu kontrollieren. Nutze den Vorteil und spring vorwärts! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limitierte Ausgabe Frühlingsausrüstung 2025.",
"weaponSpecialSpring2025MageText": "Fangschrecken Stab",
"armorSpecialSpring2025WarriorText": "Sonnenschein Rüstung",
"armorSpecialSpring2025WarriorNotes": "Diese atemberaubende Rüstung hat Farben, die du inmitten eines sonnigen Frühlingstages am Himmel sehen kannst. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Frühlingsausrüstung 2025.",
"armorSpecialSpring2025HealerText": "Plumeria Robe",
"armorSpecialSpring2025HealerNotes": "Diese atemberaubende Robe enthält Plumeria Blütenblätter, die weich und raschelig sind. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"weaponSpecialSpring2025HealerText": "Plumeria Hirtenstab",
"weaponSpecialSpring2025HealerNotes": "Mit einem Schwinger kannst du Bestäuber an deine Seite rufen, um dir bei deinen Abenteuern zu helfen. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"armorSpecialSpring2025RogueText": "Kristallnadel-Umhang",
"armorSpecialSpring2025RogueNotes": "Dieser atemberaubende Umhang enthält extra Kristalle mit speziellen, geheimen Kräften, von denen nur du weißt. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"armorSpecialSpring2025MageText": "Fangschrecken Uniform",
"armorSpecialSpring2025MageNotes": "Diese atemberaubende Uniform enthält auffällige Farben, lässt dich aber trotzdem heimlich an deine schwierigsten Aufgaben anpirschen. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"headSpecialSpring2025WarriorText": "Sonnenschein Helm",
"headSpecialSpring2025WarriorNotes": "Der Kamm auf diesem Helm erinnert an den Lauf der Sonne, sieht aber auch aus wie eine Bürste, die du für den Frühjahrsputz nutzen könntest. Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"headSpecialSpring2025RogueText": "Kristallnadel-Hut",
"headSpecialSpring2025RogueNotes": "Die Kristalle in diesem Hut ermuntern zu produktiver Energie und leuchten außerdem hell, damit du zu jeder Tages- und Nachtzeit arbeiten kannst. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"headSpecialSpring2025HealerText": "Plumeria Kopfschmuck",
"headSpecialSpring2025HealerNotes": "Diese Blume symbolisiert Geburt, Liebe und Neubeginn! Sie verbreitet auch einen schönen Duft, den du genießen kannst, während du an deinen Aufgaben arbeitest. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2025 Frühlingsausrüstung.",
"headSpecialSpring2025MageText": "Fangschrecken Maske",
"headSpecialSpring2025MageNotes": "Die Fangschrecke ist bekannt dafür, sich zu tarnen oder sich langsam zu bewegen. Wähle eine Taktik zu Hilfe für jedes einzelne Ziel, sei aber gewiss, daß du Taktiken jederzeit ändern kannst, wenn du musst. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2025 Frühlingsausrüstung."
"armorArmoirePaintersApronNotes": "Diese Schürze kann deine Kleidung vor Farbe und deine kreativen Projekte vor harschen Kritiken schützen. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Maler-Set (Gegenstand 1 von 4)."
}
+2 -5
View File
@@ -25,7 +25,7 @@
"user": "Benutzer",
"market": "Marktplatz",
"newSubscriberItem": "Du hast einen neuen <span class=\"notification-bold-blue\">Überraschungsgegenstand</span>",
"subscriberItemText": "Abonnenten bekommen jeden Monatsanfang ein neues Überraschungsausrüstungsset!",
"subscriberItemText": "Abonnenten bekommen jeden Monat einen Überraschungsgegenstand. Er wird Anfang des Monats verfügbar. Schaue auf der 'Überraschungsgegenstand'-Seite des Wikis für mehr Informationen nach.",
"all": "Alle",
"none": "Keine",
"more": "<%= count %> mehr",
@@ -238,8 +238,5 @@
"mutePlayer": "Stumm",
"skipExternalLinkModal": "Halte STRG (Windows) oder Command (Mac) beim Anklicken eines Links, um dieses Modal zu überspringen.",
"shadowMute": "Unsichtbare Stummschaltung",
"titleCustomizations": "Individualisierungen",
"targetUserNotExist": "Zielbenutzer: '<%= userName %>' existiert nicht.",
"newMessage": "Neue Nachricht",
"rememberToBeKind": "Bitte sei freundlich, respektvoll, und folge den <a href='/static/community-guidelines' target='_blank'>Community-Richtlinien</a>."
"titleCustomizations": "Individualisierungen"
}
+12 -10
View File
@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Grund für die Beschwerde",
"optional": "Optional",
"needsTextPlaceholder": "Gib Deine Nachricht hier ein.",
"copyMessageAsToDo": "Nachricht als To-Do übernehmen",
"copyAsTodo": "Als To-Do kopieren",
"messageAddedAsToDo": "Nachricht als To-Do übernommen.",
"leaderOnlyChallenges": "Nur die Gruppenleitung kann Herausforderungen erstellen",
"sendGift": "Ein Geschenk schicken",
"inviteFriends": "Lade Freunde ein",
@@ -242,7 +245,7 @@
"guildSummaryPlaceholder": "Schreibe eine Kurzbeschreibung über deine Gruppe.. Was ist der Hauptzweck der Gruppe und was werden die Gruppenmitglieder tun?",
"groupDescription": "Beschreibung",
"guildDescriptionPlaceholder": "Nutze diesen Abschnitt um alles, was Mitglieder über Deine Gruppe wissen sollten, ausführlicher darzustellen. Nützliche Tipps, hilfreiche Links und ermutigende Worte gehören hier hin!",
"markdownFormattingHelp": "[Markdown Formatierungshilfe](https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica)",
"markdownFormattingHelp": "[Markdown Formatierungshilfe](https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet)",
"partyDescriptionPlaceholder": "Das ist unsere Partybeschreibung. Sie beschreibt, was wir in unserer Party so tun. Wenn Du mehr darüber wissen willst, was wir in unserer Party so machen, lies die Beschreibung. Party on!",
"guildGemCostInfo": "Eine Edelstein-Gebühr fördert die Qualität der Gilden und wird der Gildenbank gutgeschrieben.",
"noGuildsTitle": "Du bist nicht Mitglied einer Gilde.",
@@ -288,13 +291,13 @@
"worldBossBullet4": "Besuche regelmäßig die Taverne um den Fortschritt des Weltbosses und seine Raserei-Angriffe zu prüfen",
"worldBoss": "Weltboss",
"groupPlanTitle": "Brauchst Du mehr Leute für Deine Crew?",
"groupPlanDesc": "Hausarbeiten organisieren oder ein kleines Klassenprojekt leiten? Habiticas Gruppenpläne bieten gemeinsame Aufgaben und einen eigenen Chat-Bereich, damit du und dein Team motiviert bleiben.",
"groupPlanDesc": "Ein kleines Team leiten oder Hausarbeiten organisieren? Unsere Gruppenpläne gewähren Dir exklusiven Zugang zu einem privaten Task-Board und Chat-Bereich, der Dir und Deinen Gruppenmitgliedern gewidmet ist!",
"billedMonthly": "*verrechnet als monatliches Abonnement",
"teamBasedTasksList": "Gemeinsame Aufgabenliste",
"teamBasedTasksListDesc": "Alle Gruppenmitglieder können an derselben Aufgabenliste arbeiten, um sicherzustellen, dass die Gruppe immer auf dem Laufenden bleibt. Erledige Aufgaben auf der gemeinsamen Aufgabenliste oder kopiere sie zu deinen persönlichen Aufgaben, um sie unterwegs zu erledigen.",
"groupManagementControls": "Flexible Verantwortlichkeit",
"groupManagementControlsDesc": "Teile Verantwortlichkeiten auf, indem du Aufgaben einer beliebigen Anzahl von Mitgliedern zuweist, oder stelle Aufgaben als Herausforderung ein, um zu sehen, wer sie zuerst erledigen kann. Die Gruppenmitglieder können sich über den Fortschritt der Aufgaben informieren, indem sie den Status aller Aufgaben einsehen.",
"inGameBenefits": "Alle Vorteile!",
"teamBasedTasksList": "Gruppenbasierte Aufgabenliste",
"teamBasedTasksListDesc": "Richte eine übersichtliche, gemeinsame Aufgabenliste für die Gruppe ein. Weise Deinen Gruppenmitgliedern Aufgaben zu oder lasse sie ihre eigenen Aufgaben beanspruchen, um deutlich zu machen, woran alle arbeiten!",
"groupManagementControls": "Gruppen-Management-Steuerungen",
"groupManagementControlsDesc": "Zeige den Aufgabenstatus an, um zu überprüfen, ob eine Aufgabe abgeschlossen wurde, füge Gruppen-Manager hinzu, um Verantwortlichkeiten zu teilen, und genieße einen privaten Gruppenchat für alle Gruppenmitglieder.",
"inGameBenefits": "Vorteile im Spiel",
"inGameBenefitsDesc": "Gruppenmitglieder erhalten ein exklusives Wolpertinger-Reittier sowie volle Abonnementvorteile, einschließlich spezieller monatlicher Ausrüstungssets und der Möglichkeit, Edelsteine mit Gold zu kaufen.",
"letsMakeAccount": "Lass uns Dir als erstes einen Account erstellen",
"nameYourGroup": "Wähle als nächstes einen Namen für Deine Gruppe",
@@ -375,7 +378,7 @@
"descriptionOptional": "Beschreibung",
"descriptionOptionalText": "Füge eine Beschreibung hinzu",
"nameStarText": "Füge einen Titel hinzu",
"nextPaymentMethod": "Weiter: Zahlung",
"nextPaymentMethod": "Weiter: Zahlungsmethode",
"dayStart": "<strong>Tageswechsel</strong>: <%= startTime %>",
"viewStatus": "Status",
"newGroupsWhatsNew": "Schau nach, was neu ist:",
@@ -427,6 +430,5 @@
"createGroupTitle": "Erstelle Gruppe",
"readyToUpgrade": "Bereit zum Aufrüsten?",
"interestedLearningMore": "Willst du mehr erfahren?",
"checkGroupPlanFAQ": "Schau in die <a href='/static/faq#what-is-group-plan'>Gruppenpläne FAQ</a> um herauszufinden, wie du deine gemeinsamen Aufgaben optimal nutzen kannst.",
"messageCopiedToClipboard": "Nachricht in Zwischenablage kopiert."
"checkGroupPlanFAQ": "Schau in die <a href='/static/faq#what-is-group-plan'>Gruppenpläne FAQ</a> um herauszufinden, wie du deine gemeinsamen Aufgaben optimal nutzen kannst."
}
+2 -10
View File
@@ -255,7 +255,7 @@
"dayTen": "Tag 10",
"birthdaySet": "Geburtstags-Set",
"fourForFreeText": "Um die Party am Laufen zu halten, verschenken wir Party Gewänder, 20 Edelsteine, und eine limitierte Geburtstags-Hintergrund Ausgabe und ein Gegenstände Set, das ein Cape, ein Schulterstück und eine Augenmaske enthält.",
"partyRobes": "Party-Roben",
"partyRobes": "Party Gewänder",
"twentyGems": "20 Edelsteine",
"dayOne": "Tag 1",
"summer2024WhaleSharkWarriorSet": "Walhai Set (Krieger)",
@@ -266,13 +266,5 @@
"fall2024UnderworldSorcerorMageSet": "Unterwelt Hexer Set (Magier)",
"fall2024SpaceInvaderHealerSet": "Space Invader Set (Heiler)",
"fall2024BlackCatRogueSet": "Schwarzes Katzen Set (Schurke)",
"gemSaleLimitationsText": "Dieses Angebot gilt nur während der zeitlich beschränkten Aktion. Diese Aktion startet am <%= eventStartMonth %> <%= eventStartOrdinal %> um <%= eventStartTime %> <%= timeZone %> und endet am <%= eventEndMonth %> <%= eventEndOrdinal %> um <%= eventEndTime %> <%= timeZone %>. Das Aktionsangebot ist nur verfügbar, wenn du Edelsteine für dich selbst kaufst.",
"winter2025StringLightsHealerSet": "Lichterketten Heiler Set",
"winter2025SnowRogueSet": "Schneeschurken Set",
"winter2025MooseWarriorSet": "Elchkrieger Set",
"winter2025AuroraMageSet": "Aurora Magier Set",
"spring2025PlumeriaHealerSet": "Plumeria Heiler Set",
"spring2025MantisMageSet": "Fangschrecken Magier Set",
"spring2025SunshineWarriorSet": "Sonnenschein Krieger Set",
"spring2025CrystalPointRogueSet": "Kristallspitzen Schurken Set"
"gemSaleLimitationsText": "Dieses Angebot gilt nur während der zeitlich beschränkten Aktion. Diese Aktion startet am <%= eventStartMonth %> <%= eventStartOrdinal %> um <%= eventStartTime %> lokale Zeit (<%= eventStartUTC %> UTC) und endet am <%= eventEndMonth %> <%= eventEndOrdinal %> um <%= eventEndTime %> lokale Zeit (<%= eventEndUTC %> UTC). Das Aktionsangebot ist nur verfügbar, wenn du Edelsteine für dich selbst kaufst."
}
+2
View File
@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Dieser Gegenstand kann nicht in größeren Mengen als 1 gekauft werden.",
"notificationsRequired": "Mitteilungs-IDs werden benötigt.",
"unallocatedStatsPoints": "Du kannst <span class=\"notification-bold-blue\"><%= points %> Attributpunkt(e)</span> verteilen",
"beginningOfConversation": "Dies ist der Anfang Deiner Unterhaltung mit <%= userName %>.",
"messageDeletedUser": "Tut uns leid, dieser Benutzer hat sein Konto gelöscht.",
"messageMissingDisplayName": "Fehlender Anzeigename.",
"reportedMessage": "Du hast diese Nachricht den Moderatoren gemeldet.",
"canDeleteNow": "Du kannst diese Nachricht nun löschen, wenn Du willst.",
"beginningOfConversationReminder": "Denke an einen freundlichen und respektvollen Umgang und halte Dich an die Community-Richtlinien!",
"newsPostNotFound": "News-Eintrag nicht gefunden, oder Du hast keinen Zugriff.",
"messagePetMountUnEquipped": "Haus- und Reittier in die Stallungen gebracht.",
"messageCostumeUnEquipped": "Kostüm abgelegt.",

Some files were not shown because too many files have changed in this diff Show More