mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-11 02:28:50 -05:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f440d9097 | |||
| 0294868747 | |||
| 9c8d870d16 | |||
| a7acd863f3 | |||
| f32ef0a6ba | |||
| ebf3b4aa47 | |||
| 5a8366468b | |||
| df57518815 | |||
| 7d342b5115 | |||
| 388de9a97d | |||
| 28c79d9d20 | |||
| 85cf322b30 | |||
| 362ca73c94 | |||
| 90273362c4 | |||
| 7aadc10fab | |||
| 9e008890b2 | |||
| 5505bf1e45 | |||
| d40781ce07 | |||
| d9719cdc05 | |||
| 8cc6a96be0 | |||
| c5fb2d6506 | |||
| e24a024091 | |||
| dc7d3816fd | |||
| db9c13a05d | |||
| 8c8aa78a1a | |||
| 6e3f7c005a | |||
| 1395380dfe | |||
| 833ceb3bf3 | |||
| 0522aa1551 | |||
| 71c0939a15 | |||
| 26c8323e70 | |||
| bb7d447003 | |||
| 4394772ee3 | |||
| 6ec23ce790 | |||
| b953519e2d |
+1
-1
@@ -17,7 +17,7 @@
|
||||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
||||
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
|
||||
"NODE_ENV":"development",
|
||||
"ENABLE_CONSOLE_LOGS_IN_TEST": false,
|
||||
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
|
||||
"CRON_SAFE_MODE":"false",
|
||||
"CRON_SEMI_SAFE_MODE":"false",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
const migrationName = '20180811_inboxOutsideUser.js';
|
||||
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Move inbox messages from the user model to their own collection
|
||||
*/
|
||||
|
||||
const monk = require('monk');
|
||||
const nconf = require('nconf');
|
||||
|
||||
const Inbox = require('../website/server/models/message').inboxModel;
|
||||
const connectionString = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||
const dbInboxes = monk(connectionString).get('inboxes', { castIds: false });
|
||||
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 1000,
|
||||
fields: ['_id', 'inbox'],
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
let msgCount = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users and their tasks found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let usersPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(usersPromises)
|
||||
.then(() => {
|
||||
return processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (msgCount % progressCount === 0) console.warn(`${msgCount } messages processed`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } being processed`);
|
||||
|
||||
const oldInboxMessages = user.inbox.messages || {};
|
||||
const oldInboxMessagesIds = Object.keys(oldInboxMessages);
|
||||
|
||||
msgCount += oldInboxMessagesIds.length;
|
||||
|
||||
const newInboxMessages = oldInboxMessagesIds.map(msgId => {
|
||||
const msg = oldInboxMessages[msgId];
|
||||
if (!msg || (!msg.id && !msg._id)) { // eslint-disable-line no-extra-parens
|
||||
console.log('missing message or message _id and id', msg);
|
||||
throw new Error('error!');
|
||||
}
|
||||
|
||||
if (msg.id && !msg._id) msg._id = msg.id;
|
||||
if (msg._id && !msg.id) msg.id = msg._id;
|
||||
|
||||
const newMsg = new Inbox(msg);
|
||||
newMsg.ownerId = user._id;
|
||||
return newMsg.toJSON();
|
||||
});
|
||||
|
||||
return dbInboxes.insert(newInboxMessages)
|
||||
.then(() => {
|
||||
return dbUsers.update({_id: user._id}, {
|
||||
$set: {
|
||||
migration: migrationName,
|
||||
'inbox.messages': {},
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
console.warn(`\n${ msgCount } messages processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -0,0 +1,99 @@
|
||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Generate usernames for users who lack them
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
import { generateUsername } from '../../website/server/libs/auth/utils';
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
'auth.local.username': {$exists: false},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2018-04-01')}, // Initial coverage for users active within last 6 months
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'auth',
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
if (!user.auth.local.username) {
|
||||
const newName = generateUsername();
|
||||
dbUsers.update(
|
||||
{_id: user._id},
|
||||
{$set:
|
||||
{
|
||||
'auth.local.username': newName,
|
||||
'auth.local.lowerCaseUsername': newName,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
Generated
+7777
-7172
File diff suppressed because it is too large
Load Diff
+46
-45
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.62.1",
|
||||
"version": "4.63.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -10,13 +10,13 @@
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^8.6.5",
|
||||
"aws-sdk": "^2.317.0",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.239.1",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.6",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-syntax-async-functions": "^6.13.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||
@@ -26,48 +26,48 @@
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"bcrypt": "github:MylesBorins/node.bcrypt.js#update-nan",
|
||||
"bcrypt": "^3.0.1",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.9",
|
||||
"compression": "^1.7.3",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"cross-env": "^5.1.5",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^3.0.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.3",
|
||||
"express-basic-auth": "^1.1.5",
|
||||
"express-validator": "^5.3.0",
|
||||
"express-validator": "^5.2.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"glob": "^7.1.3",
|
||||
"got": "^9.2.2",
|
||||
"glob": "^7.1.2",
|
||||
"got": "^9.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
"gulp-nodemon": "^2.2.1",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.17.1",
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"image-size": "^0.6.3",
|
||||
"in-app-purchase": "^1.10.1",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.9.4",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.10",
|
||||
"merge-stream": "^1.0.0",
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.2.15",
|
||||
"morgan": "^1.9.1",
|
||||
"mongoose": "^5.1.3",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^0.14.4",
|
||||
"node-sass": "^4.9.3",
|
||||
"nodemailer": "^4.6.8",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^4.6.4",
|
||||
"ora": "^2.1.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
@@ -75,41 +75,42 @@
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"popper.js": "^1.14.4",
|
||||
"popper.js": "^1.14.3",
|
||||
"postcss-easy-import": "^3.0.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.3",
|
||||
"pusher": "^1.3.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"sass-loader": "^7.0.0",
|
||||
"shelljs": "^0.8.2",
|
||||
"short-uuid": "^3.0.0",
|
||||
"smartbanner.js": "^1.9.1",
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^3.8.3",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.1.1",
|
||||
"svgo-loader": "^2.2.0",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"svgo": "^1.0.5",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.16",
|
||||
"update": "^0.7.4",
|
||||
"upgrade": "^1.1.0",
|
||||
"url-loader": "^1.1.1",
|
||||
"url-loader": "^1.0.0",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.3.2",
|
||||
"validator": "^10.7.1",
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^10.5.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.5.17",
|
||||
"vue": "^2.5.16",
|
||||
"vue-loader": "^14.2.2",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-merge": "^4.1.4",
|
||||
"winston": "^2.4.4",
|
||||
"winston-loggly-bulk": "^2.0.3",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.4.2",
|
||||
"winston-loggly-bulk": "^2.0.2",
|
||||
"xml2js": "^0.4.4"
|
||||
},
|
||||
"private": true,
|
||||
@@ -144,22 +145,22 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^1.0.0-beta.25",
|
||||
"@vue/test-utils": "^1.0.0-beta.16",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.41.0",
|
||||
"chromedriver": "^2.38.3",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.2",
|
||||
"coveralls": "^3.0.1",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-habitrpg": "^4.0.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^2.1.0",
|
||||
"eslint-plugin-html": "^4.0.5",
|
||||
"eslint-plugin-mocha": "^5.2.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-html": "^4.0.3",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.18.0",
|
||||
@@ -175,20 +176,20 @@
|
||||
"karma-sinon-stub-promise": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-webpack": "^3.0.5",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"lcov-result-merger": "^3.0.0",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^6.0.6",
|
||||
"nightwatch": "^0.9.21",
|
||||
"puppeteer": "^1.8.0",
|
||||
"puppeteer": "^1.4.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.14.0",
|
||||
"selenium-server": "^3.12.0",
|
||||
"sinon": "^4.5.0",
|
||||
"sinon-chai": "^3.2.0",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.12.0",
|
||||
"webpack-dev-middleware": "^2.0.5",
|
||||
"webpack-hot-middleware": "^2.24.0"
|
||||
"webpack-hot-middleware": "^2.22.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"memwatch-next": "^0.3.0",
|
||||
|
||||
@@ -178,4 +178,12 @@ describe('taskManager', () => {
|
||||
|
||||
expect(order).to.eql(['task-id-2', 'task-id-1']);
|
||||
});
|
||||
|
||||
it('moves tasks to a specified position out of length', async () => {
|
||||
let order = ['task-id-1'];
|
||||
|
||||
moveTask(order, 'task-id-2', 2);
|
||||
|
||||
expect(order).to.eql(['task-id-1', 'task-id-2']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1020,32 +1020,6 @@ describe('Group Model', () => {
|
||||
expect(chat.user).to.not.exist;
|
||||
});
|
||||
|
||||
it('cuts down chat to 200 messages', () => {
|
||||
for (let i = 0; i < 220; i++) {
|
||||
party.chat.push({ text: 'a message' });
|
||||
}
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(220);
|
||||
|
||||
party.sendChat('message');
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(200);
|
||||
});
|
||||
|
||||
it('cuts down chat to 400 messages when group is subcribed', () => {
|
||||
party.purchased.plan.customerId = 'test-customer-id';
|
||||
|
||||
for (let i = 0; i < 420; i++) {
|
||||
party.chat.push({ text: 'a message' });
|
||||
}
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(420);
|
||||
|
||||
party.sendChat('message');
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(400);
|
||||
});
|
||||
|
||||
it('updates users about new messages in party', () => {
|
||||
party.sendChat('message');
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ describe('GET /inbox/messages', () => {
|
||||
|
||||
// message to yourself
|
||||
expect(messages[0].text).to.equal('fourth');
|
||||
expect(messages[0].sent).to.equal(false);
|
||||
expect(messages[0].uuid).to.equal(user._id);
|
||||
|
||||
expect(messages[1].text).to.equal('third');
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
generateUser,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
|
||||
describe('POST /groups/:groupId/quests/accept', () => {
|
||||
const PET_QUEST = 'whale';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
generateUser,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
|
||||
describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
const PET_QUEST = 'whale';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { quests as questScrolls } from '../../../../../website/common/script/content';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
|
||||
describe('POST /groups/:groupId/quests/reject', () => {
|
||||
let questingGroup;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
createAndPopulateGroup, translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { find } from 'lodash';
|
||||
import {find} from 'lodash';
|
||||
|
||||
describe('PUT /tasks/:id', () => {
|
||||
let user, guild, member, member2, task;
|
||||
@@ -38,16 +38,64 @@ describe('PUT /tasks/:id', () => {
|
||||
|
||||
it('updates a group task', async () => {
|
||||
let savedHabit = await user.put(`/tasks/${task._id}`, {
|
||||
text: 'some new text',
|
||||
up: false,
|
||||
down: false,
|
||||
notes: 'some new notes',
|
||||
});
|
||||
|
||||
expect(savedHabit.text).to.eql('some new text');
|
||||
expect(savedHabit.notes).to.eql('some new notes');
|
||||
expect(savedHabit.up).to.eql(false);
|
||||
expect(savedHabit.down).to.eql(false);
|
||||
});
|
||||
|
||||
it('updates a group task - approval is required', async () => {
|
||||
// allow to manage
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member._id,
|
||||
});
|
||||
|
||||
// change the todo
|
||||
task = await member.put(`/tasks/${task._id}`, {
|
||||
text: 'new text!',
|
||||
requiresApproval: true,
|
||||
});
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, (memberTask) => memberTask.group.taskId === task._id);
|
||||
|
||||
// score up to trigger approval
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
});
|
||||
|
||||
it('updates a group task with checklist', async () => {
|
||||
// add a new todo
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo',
|
||||
type: 'todo',
|
||||
checklist: [
|
||||
{
|
||||
text: 'checklist 1',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
// change the checklist text
|
||||
task = await user.put(`/tasks/${task._id}`, {
|
||||
checklist: [
|
||||
{
|
||||
id: task.checklist[0].id,
|
||||
text: 'checklist 1 - edit',
|
||||
},
|
||||
{
|
||||
text: 'checklist 2 - edit',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(task.checklist.length).to.eql(2);
|
||||
});
|
||||
|
||||
it('updates the linked tasks', async () => {
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('PUT /user', () => {
|
||||
});
|
||||
|
||||
|
||||
it('profile.name cannot be an empty string or null', async () => {
|
||||
it('validates profile.name', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': ' ', // string should be trimmed
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
@@ -76,7 +76,23 @@ describe('PUT /user', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': 'this is a very long display name that will not be allowed due to length',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('displaynameIssueLength'),
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': 'TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('displaynameIssueSlur'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,23 @@ describe('POST /user/auth/local/register', () => {
|
||||
expect(user.newUser).to.eql(true);
|
||||
});
|
||||
|
||||
it('registers a new user and sets verifiedUsername to true', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user._id).to.exist;
|
||||
expect(user.apiToken).to.exist;
|
||||
expect(user.flags.verifiedUsername).to.eql(true);
|
||||
});
|
||||
|
||||
xit('remove spaces from username', async () => {
|
||||
// TODO can probably delete this test now
|
||||
let username = ' usernamewithspaces ';
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('POST /user/auth/social', () => {
|
||||
});
|
||||
|
||||
it('registers a new user', async () => {
|
||||
let response = await api.post(endpoint, {
|
||||
const response = await api.post(endpoint, {
|
||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||
network,
|
||||
});
|
||||
@@ -47,7 +47,10 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
expect(response.username).to.exist;
|
||||
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user');
|
||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.exist;
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
|
||||
@@ -8,7 +8,11 @@ describe('POST /user/allocate', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
@@ -31,6 +35,16 @@ describe('POST /user/allocate', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the user hasn\'t selected class', async () => {
|
||||
await user.update({'flags.classSelected': false});
|
||||
await expect(user.post('/user/allocate'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('classNotSelected'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allocates attribute points', async () => {
|
||||
await user.update({'stats.points': 1});
|
||||
let res = await user.post('/user/allocate?stat=con');
|
||||
|
||||
@@ -13,7 +13,11 @@ describe('POST /user/allocate-bulk', () => {
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
@@ -27,6 +31,16 @@ describe('POST /user/allocate-bulk', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user has not selected class', async () => {
|
||||
await user.update({'flags.classSelected': false});
|
||||
await expect(user.post('/user/allocate-bulk', statsUpdate))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('classNotSelected'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allocates attribute points', async () => {
|
||||
await user.update({'stats.points': 3});
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
resetHabiticaDB,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /coupons/enter/:code', () => {
|
||||
let user;
|
||||
let sudoUser;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
sudoUser = await generateUser({
|
||||
'contributor.sudo': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if code is missing', async () => {
|
||||
await expect(user.post('/coupons/enter')).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Not found.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if code is invalid', async () => {
|
||||
await expect(user.post('/coupons/enter/notValid')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidCoupon'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if coupon has been used', async () => {
|
||||
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
|
||||
await user.post(`/coupons/enter/${coupon._id}`); // use coupon
|
||||
|
||||
await expect(user.post(`/coupons/enter/${coupon._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('couponUsed'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply the coupon to the user', async () => {
|
||||
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
|
||||
let userRes = await user.post(`/coupons/enter/${coupon._id}`);
|
||||
expect(userRes._id).to.equal(user._id);
|
||||
expect(userRes.items.gear.owned.eyewear_special_wondercon_red).to.be.true;
|
||||
expect(userRes.items.gear.owned.eyewear_special_wondercon_black).to.be.true;
|
||||
expect(userRes.items.gear.owned.back_special_wondercon_black).to.be.true;
|
||||
expect(userRes.items.gear.owned.back_special_wondercon_red).to.be.true;
|
||||
expect(userRes.items.gear.owned.body_special_wondercon_red).to.be.true;
|
||||
expect(userRes.items.gear.owned.body_special_wondercon_black).to.be.true;
|
||||
expect(userRes.items.gear.owned.body_special_wondercon_gold).to.be.true;
|
||||
expect(userRes.extra).to.eql({signupEvent: 'wondercon'});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('DELETE /inbox/clear', () => {
|
||||
it('removes all inbox messages for the user', async () => {
|
||||
const [user, otherUser] = await Promise.all([generateUser(), generateUser()]);
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'first',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'second',
|
||||
});
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'third',
|
||||
});
|
||||
|
||||
let messages = await user.get('/inbox/messages');
|
||||
expect(messages.length).to.equal(3);
|
||||
|
||||
await user.del('/inbox/clear/');
|
||||
|
||||
messages = await user.get('/inbox/messages');
|
||||
expect(messages.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
import common from '../../../../website/common';
|
||||
|
||||
describe('GET /user', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns the authenticated user with computed stats', async () => {
|
||||
let returnedUser = await user.get('/user');
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
|
||||
expect(returnedUser.stats.maxMP).to.exist;
|
||||
expect(returnedUser.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(returnedUser.stats.toNextLevel).to.equal(common.tnl(returnedUser.stats.lvl));
|
||||
});
|
||||
|
||||
it('does not return private paths (and apiToken)', async () => {
|
||||
let returnedUser = await user.get('/user');
|
||||
|
||||
expect(returnedUser.auth.local.hashed_password).to.not.exist;
|
||||
expect(returnedUser.auth.local.passwordHashMethod).to.not.exist;
|
||||
expect(returnedUser.auth.local.salt).to.not.exist;
|
||||
expect(returnedUser.apiToken).to.not.exist;
|
||||
});
|
||||
|
||||
it('returns only user properties requested', async () => {
|
||||
let returnedUser = await user.get('/user?userFields=achievements,items.mounts');
|
||||
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
expect(returnedUser.achievements).to.exist;
|
||||
expect(returnedUser.items.mounts).to.exist;
|
||||
// Notifications are always returned
|
||||
expect(returnedUser.notifications).to.exist;
|
||||
expect(returnedUser.stats).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not return new inbox messages', async () => {
|
||||
const otherUser = await generateUser();
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'first',
|
||||
});
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'second',
|
||||
});
|
||||
let returnedUser = await user.get('/user');
|
||||
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
expect(returnedUser.inbox.messages).to.be.empty;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,324 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
generateGroup,
|
||||
generateChallenge,
|
||||
sleep,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { find } from 'lodash';
|
||||
import apiError from '../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /user/class/cast/:spellId', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns an error if spell does not exist', async () => {
|
||||
await user.update({'stats.class': 'rogue'});
|
||||
let spellId = 'invalidSpell';
|
||||
await expect(user.post(`/user/class/cast/${spellId}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: apiError('spellNotFound', {spellId}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell does not exist in user\'s class', async () => {
|
||||
let spellId = 'pickPocket';
|
||||
await expect(user.post(`/user/class/cast/${spellId}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: apiError('spellNotFound', {spellId}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.mana > user.mana', async () => {
|
||||
await user.update({'stats.class': 'rogue'});
|
||||
await expect(user.post('/user/class/cast/backStab'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notEnoughMana'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.value > user.gold', async () => {
|
||||
await expect(user.post('/user/class/cast/birthday'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageNotEnoughGold'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.lvl > user.level', async () => {
|
||||
await user.update({'stats.mp': 200, 'stats.class': 'wizard'});
|
||||
await expect(user.post('/user/class/cast/earth'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('spellLevelTooHigh', {level: 13}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user doesn\'t own the spell', async () => {
|
||||
await expect(user.post('/user/class/cast/snowball'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('spellNotOwned'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targetId is not an UUID', async () => {
|
||||
await expect(user.post('/user/class/cast/spellId?targetId=notAnUUID'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targetId is required but missing', async () => {
|
||||
await user.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await expect(user.post('/user/class/cast/pickPocket'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('targetIdUUID'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targeted task doesn\'t exist', async () => {
|
||||
await user.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await expect(user.post(`/user/class/cast/pickPocket?targetId=${generateUUID()}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if a challenge task was targeted', async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup();
|
||||
let challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
|
||||
{type: 'habit', text: 'task text'},
|
||||
]);
|
||||
await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await sleep(0.5);
|
||||
await groupLeader.sync();
|
||||
await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${groupLeader.tasksOrder.habits[0]}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('challengeTasksNoCast'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if a group task was targeted', async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup();
|
||||
|
||||
let groupTask = await groupLeader.post(`/tasks/group/${group._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await groupLeader.post(`/tasks/${groupTask._id}/assign/${groupLeader._id}`);
|
||||
let memberTasks = await groupLeader.get('/tasks/user');
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === group._id;
|
||||
});
|
||||
|
||||
await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await sleep(0.5);
|
||||
await groupLeader.sync();
|
||||
|
||||
await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${syncedGroupTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('groupTasksNoCast'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targeted party member doesn\'t exist', async () => {
|
||||
let {groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 1,
|
||||
});
|
||||
await groupLeader.update({'items.special.snowball': 3});
|
||||
|
||||
let target = generateUUID();
|
||||
await expect(groupLeader.post(`/user/class/cast/snowball?targetId=${target}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', {userId: target}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if party does not exists', async () => {
|
||||
await user.update({'items.special.snowball': 3});
|
||||
|
||||
await expect(user.post(`/user/class/cast/snowball?targetId=${generateUUID()}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('partyNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('send message in party chat if party && !spell.silent', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 1,
|
||||
});
|
||||
await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13});
|
||||
|
||||
await groupLeader.post('/user/class/cast/earth');
|
||||
await sleep(1);
|
||||
const groupMessages = await groupLeader.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(groupMessages[0]).to.exist;
|
||||
expect(groupMessages[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
it('Ethereal Surge does not recover mp of other mages', async () => {
|
||||
let group = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 4,
|
||||
});
|
||||
|
||||
let promises = [];
|
||||
promises.push(group.groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[0].update({'stats.mp': 0, 'stats.class': 'warrior', 'stats.lvl': 20}));
|
||||
promises.push(group.members[1].update({'stats.mp': 0, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[2].update({'stats.mp': 0, 'stats.class': 'rogue', 'stats.lvl': 20}));
|
||||
promises.push(group.members[3].update({'stats.mp': 0, 'stats.class': 'healer', 'stats.lvl': 20}));
|
||||
await Promise.all(promises);
|
||||
|
||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||
|
||||
promises = [];
|
||||
promises.push(group.members[0].sync());
|
||||
promises.push(group.members[1].sync());
|
||||
promises.push(group.members[2].sync());
|
||||
promises.push(group.members[3].sync());
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||
});
|
||||
|
||||
it('cast bulk', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13});
|
||||
await groupLeader.post('/user/class/cast/earth', {quantity: 2});
|
||||
|
||||
await sleep(1);
|
||||
group = await groupLeader.get(`/groups/${group._id}`);
|
||||
|
||||
expect(group.chat[0]).to.exist;
|
||||
expect(group.chat[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
it('searing brightness does not affect challenge or group tasks', async () => {
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test challenge habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await user.update({'stats.class': 'healer', 'stats.mp': 200, 'stats.lvl': 15});
|
||||
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
|
||||
|
||||
await user.post('/user/class/cast/brightness');
|
||||
await user.sync();
|
||||
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === guild._id;
|
||||
});
|
||||
|
||||
let userChallengeTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.challenge.id === challenge._id;
|
||||
});
|
||||
|
||||
expect(userChallengeTask).to.exist;
|
||||
expect(syncedGroupTask).to.exist;
|
||||
expect(userChallengeTask.value).to.equal(0);
|
||||
expect(syncedGroupTask.value).to.equal(0);
|
||||
});
|
||||
|
||||
it('increases both user\'s achievement values', async () => {
|
||||
let party = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
});
|
||||
let leader = party.groupLeader;
|
||||
let recipient = party.members[0];
|
||||
await leader.update({'stats.gp': 10});
|
||||
await leader.post(`/user/class/cast/birthday?targetId=${recipient._id}`);
|
||||
await leader.sync();
|
||||
await recipient.sync();
|
||||
expect(leader.achievements.birthday).to.equal(1);
|
||||
expect(recipient.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('only increases user\'s achievement one if target == caster', async () => {
|
||||
await user.update({'stats.gp': 10});
|
||||
await user.post(`/user/class/cast/birthday?targetId=${user._id}`);
|
||||
await user.sync();
|
||||
expect(user.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('passes correct target to spell when targetType === \'task\'', async () => {
|
||||
await user.update({'stats.class': 'wizard', 'stats.lvl': 11});
|
||||
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
let result = await user.post(`/user/class/cast/fireball?targetId=${task._id}`);
|
||||
|
||||
expect(result.task._id).to.equal(task._id);
|
||||
});
|
||||
|
||||
it('passes correct target to spell when targetType === \'self\'', async () => {
|
||||
await user.update({'stats.class': 'wizard', 'stats.lvl': 14, 'stats.mp': 50});
|
||||
|
||||
let result = await user.post('/user/class/cast/frost');
|
||||
|
||||
expect(result.user.stats.mp).to.equal(10);
|
||||
});
|
||||
|
||||
|
||||
// TODO find a way to have sinon working in integration tests
|
||||
// it doesn't work when tests are running separately from server
|
||||
it('passes correct target to spell when targetType === \'tasks\'');
|
||||
it('passes correct target to spell when targetType === \'party\'');
|
||||
it('passes correct target to spell when targetType === \'user\'');
|
||||
it('passes correct target to spell when targetType === \'party\' and user is not in a party');
|
||||
it('passes correct target to spell when targetType === \'user\' and user is not in a party');
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateDaily,
|
||||
generateReward,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /user/rebirth', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns an error when user balance is too low', async () => {
|
||||
await expect(user.post('/user/rebirth'))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notEnoughGems'),
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('resets user\'s tasks', async () => {
|
||||
await user.update({
|
||||
balance: 1.5,
|
||||
});
|
||||
|
||||
let daily = await generateDaily({
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
value: 1,
|
||||
streak: 1,
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let reward = await generateReward({
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
value: 1,
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let response = await user.post('/user/rebirth');
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('REBIRTH_ACHIEVEMENT');
|
||||
|
||||
let updatedDaily = await user.get(`/tasks/${daily._id}`);
|
||||
let updatedReward = await user.get(`/tasks/${reward._id}`);
|
||||
|
||||
expect(response.message).to.equal(t('rebirthComplete'));
|
||||
expect(updatedDaily.streak).to.equal(0);
|
||||
expect(updatedDaily.value).to.equal(0);
|
||||
expect(updatedReward.value).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateDaily,
|
||||
generateReward,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /user/reroll', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns an error when user balance is too low', async () => {
|
||||
await expect(user.post('/user/reroll'))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notEnoughGems'),
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('resets user\'s tasks', async () => {
|
||||
await user.update({
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
let daily = await generateDaily({
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let reward = await generateReward({
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
value: 1,
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let response = await user.post('/user/reroll');
|
||||
await user.sync();
|
||||
|
||||
let updatedDaily = await user.get(`/tasks/${daily._id}`);
|
||||
let updatedReward = await user.get(`/tasks/${reward._id}`);
|
||||
|
||||
expect(response.message).to.equal(t('fortifyComplete'));
|
||||
expect(updatedDaily.value).to.equal(0);
|
||||
expect(updatedReward.value).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
generateChallenge,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /user/reset', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('resets user\'s habits', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('resets user\'s dailys', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.dailys).to.be.empty;
|
||||
});
|
||||
|
||||
it('resets user\'s todos', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.todos).to.be.empty;
|
||||
});
|
||||
|
||||
it('resets user\'s rewards', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('does not delete challenge or group tasks', async () => {
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test challenge habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === guild._id;
|
||||
});
|
||||
|
||||
let userChallengeTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.challenge.id === challenge._id;
|
||||
});
|
||||
|
||||
expect(userChallengeTask).to.exist;
|
||||
expect(syncedGroupTask).to.exist;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,256 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
import { each, get } from 'lodash';
|
||||
|
||||
describe('PUT /user', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
context('Allowed Operations', () => {
|
||||
it('updates the user', async () => {
|
||||
await user.put('/user', {
|
||||
'profile.name': 'Frodo',
|
||||
'preferences.costume': true,
|
||||
'stats.hp': 14,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.profile.name).to.eql('Frodo');
|
||||
expect(user.preferences.costume).to.eql(true);
|
||||
expect(user.stats.hp).to.eql(14);
|
||||
});
|
||||
|
||||
it('tags must be an array', async () => {
|
||||
await expect(user.put('/user', {
|
||||
tags: {
|
||||
tag: true,
|
||||
},
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'mustBeArray',
|
||||
});
|
||||
});
|
||||
|
||||
it('update tags', async () => {
|
||||
let userTags = user.tags;
|
||||
|
||||
await user.put('/user', {
|
||||
tags: [...user.tags, {
|
||||
name: 'new tag',
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.tags.length).to.be.eql(userTags.length + 1);
|
||||
});
|
||||
|
||||
|
||||
it('profile.name cannot be an empty string or null', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': ' ', // string should be trimmed
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': null,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Top Level Protected Operations', () => {
|
||||
let protectedOperations = {
|
||||
'gem balance': {balance: 100},
|
||||
auth: {'auth.blocked': true, 'auth.timestamps.created': new Date()},
|
||||
contributor: {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'},
|
||||
backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'},
|
||||
subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000},
|
||||
'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true},
|
||||
notifications: [{type: 123}],
|
||||
webhooks: {webhooks: [{url: 'https://foobar.com'}]},
|
||||
};
|
||||
|
||||
each(protectedOperations, (data, testName) => {
|
||||
it(`does not allow updating ${testName}`, async () => {
|
||||
let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] });
|
||||
|
||||
await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: errorText,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Sub-Level Protected Operations', () => {
|
||||
let protectedOperations = {
|
||||
'class stat': {'stats.class': 'wizard'},
|
||||
'flags unless whitelisted': {'flags.dropsEnabled': true},
|
||||
webhooks: {'preferences.webhooks': [1, 2, 3]},
|
||||
sleep: {'preferences.sleep': true},
|
||||
'disable classes': {'preferences.disableClasses': true},
|
||||
};
|
||||
|
||||
each(protectedOperations, (data, testName) => {
|
||||
it(`does not allow updating ${testName}`, async () => {
|
||||
let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] });
|
||||
|
||||
await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: errorText,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Default Appearance Preferences', () => {
|
||||
let testCases = {
|
||||
shirt: 'yellow',
|
||||
skin: 'ddc994',
|
||||
'hair.color': 'blond',
|
||||
'hair.bangs': 2,
|
||||
'hair.base': 1,
|
||||
'hair.flower': 4,
|
||||
size: 'broad',
|
||||
};
|
||||
|
||||
each(testCases, (item, type) => {
|
||||
const update = {};
|
||||
update[`preferences.${type}`] = item;
|
||||
|
||||
it(`updates user with ${type} that is a default`, async () => {
|
||||
let dbUpdate = {};
|
||||
dbUpdate[`purchased.${type}.${item}`] = true;
|
||||
await user.update(dbUpdate);
|
||||
|
||||
// Sanity checks to make sure user is not already equipped with item
|
||||
expect(get(user.preferences, type)).to.not.eql(item);
|
||||
|
||||
let updatedUser = await user.put('/user', update);
|
||||
|
||||
expect(get(updatedUser.preferences, type)).to.eql(item);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user tries to update body size with invalid type', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'preferences.size': 'round',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('mustPurchaseToSet', { val: 'round', key: 'preferences.size' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('can set beard to default', async () => {
|
||||
await user.update({
|
||||
'purchased.hair.beard': 3,
|
||||
'preferences.hair.beard': 3,
|
||||
});
|
||||
|
||||
let updatedUser = await user.put('/user', {
|
||||
'preferences.hair.beard': 0,
|
||||
});
|
||||
|
||||
expect(updatedUser.preferences.hair.beard).to.eql(0);
|
||||
});
|
||||
|
||||
it('can set mustache to default', async () => {
|
||||
await user.update({
|
||||
'purchased.hair.mustache': 2,
|
||||
'preferences.hair.mustache': 2,
|
||||
});
|
||||
|
||||
let updatedUser = await user.put('/user', {
|
||||
'preferences.hair.mustache': 0,
|
||||
});
|
||||
|
||||
expect(updatedUser.preferences.hair.mustache).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('Purchasable Appearance Preferences', () => {
|
||||
let testCases = {
|
||||
background: 'volcano',
|
||||
shirt: 'convict',
|
||||
skin: 'cactus',
|
||||
'hair.base': 7,
|
||||
'hair.beard': 2,
|
||||
'hair.color': 'rainbow',
|
||||
'hair.mustache': 2,
|
||||
};
|
||||
|
||||
each(testCases, (item, type) => {
|
||||
const update = {};
|
||||
update[`preferences.${type}`] = item;
|
||||
|
||||
it(`returns an error if user tries to update ${type} with ${type} the user does not own`, async () => {
|
||||
await expect(user.put('/user', update)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('mustPurchaseToSet', {val: item, key: `preferences.${type}`}),
|
||||
});
|
||||
});
|
||||
|
||||
it(`updates user with ${type} user does own`, async () => {
|
||||
let dbUpdate = {};
|
||||
dbUpdate[`purchased.${type}.${item}`] = true;
|
||||
await user.update(dbUpdate);
|
||||
|
||||
// Sanity check to make sure user is not already equipped with item
|
||||
expect(get(user.preferences, type)).to.not.eql(item);
|
||||
|
||||
let updatedUser = await user.put('/user', update);
|
||||
|
||||
expect(get(updatedUser.preferences, type)).to.eql(item);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Improvement Categories', () => {
|
||||
it('sets valid categories', async () => {
|
||||
await user.put('/user', {
|
||||
'preferences.improvementCategories': ['work', 'school'],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.preferences.improvementCategories).to.eql(['work', 'school']);
|
||||
});
|
||||
|
||||
it('discards invalid categories', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'preferences.improvementCategories': ['work', 'procrastination', 'school'],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,739 @@
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
getProperty,
|
||||
} from '../../../../helpers/api-integration/v4';
|
||||
import { ApiUser } from '../../../../helpers/api-integration/api-classes';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { each } from 'lodash';
|
||||
import { encrypt } from '../../../../../website/server/libs/encryption';
|
||||
|
||||
function generateRandomUserName () {
|
||||
return (Date.now() + uuid()).substring(0, 20);
|
||||
}
|
||||
|
||||
describe('POST /user/auth/local/register', () => {
|
||||
context('username and email are free', () => {
|
||||
let api;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
});
|
||||
|
||||
it('registers a new user', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user._id).to.exist;
|
||||
expect(user.apiToken).to.exist;
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.profile.name).to.eql(username);
|
||||
expect(user.newUser).to.eql(true);
|
||||
});
|
||||
|
||||
xit('remove spaces from username', async () => {
|
||||
// TODO can probably delete this test now
|
||||
let username = ' usernamewithspaces ';
|
||||
let email = 'test@example.com';
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.username).to.eql(username.trim());
|
||||
expect(user.profile.name).to.eql(username.trim());
|
||||
});
|
||||
|
||||
context('validates username', () => {
|
||||
const email = 'test@example.com';
|
||||
const password = 'password';
|
||||
|
||||
it('requires to username to be less than 20', async () => {
|
||||
const username = (Date.now() + uuid()).substring(0, 21);
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects chracters not in [-_a-zA-Z0-9]', async () => {
|
||||
const username = 'a-zA_Z09*';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows only [-_a-zA-Z0-9] characters', async () => {
|
||||
const username = 'a-zA_Z09';
|
||||
|
||||
const user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
});
|
||||
});
|
||||
|
||||
context('provides default tags and tasks', async () => {
|
||||
it('for a generic API consumer', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(1);
|
||||
expect(rewards).to.have.a.lengthOf(0);
|
||||
|
||||
expect(tags).to.have.a.lengthOf(7);
|
||||
expect(tags[0].name).to.eql(t('defaultTag1'));
|
||||
expect(tags[1].name).to.eql(t('defaultTag2'));
|
||||
expect(tags[2].name).to.eql(t('defaultTag3'));
|
||||
expect(tags[3].name).to.eql(t('defaultTag4'));
|
||||
expect(tags[4].name).to.eql(t('defaultTag5'));
|
||||
expect(tags[5].name).to.eql(t('defaultTag6'));
|
||||
expect(tags[6].name).to.eql(t('defaultTag7'));
|
||||
});
|
||||
|
||||
xit('for Web', async () => {
|
||||
api = requester(
|
||||
null,
|
||||
{'x-client': 'habitica-web'},
|
||||
);
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(3);
|
||||
expect(habits[0].text).to.eql(t('defaultHabit1Text'));
|
||||
expect(habits[0].notes).to.eql('');
|
||||
expect(habits[1].text).to.eql(t('defaultHabit2Text'));
|
||||
expect(habits[1].notes).to.eql('');
|
||||
expect(habits[2].text).to.eql(t('defaultHabit3Text'));
|
||||
expect(habits[2].notes).to.eql('');
|
||||
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
|
||||
expect(todos).to.have.a.lengthOf(1);
|
||||
expect(todos[0].text).to.eql(t('defaultTodo1Text'));
|
||||
expect(todos[0].notes).to.eql(t('defaultTodoNotes'));
|
||||
|
||||
expect(rewards).to.have.a.lengthOf(1);
|
||||
expect(rewards[0].text).to.eql(t('defaultReward1Text'));
|
||||
expect(rewards[0].notes).to.eql('');
|
||||
|
||||
expect(tags).to.have.a.lengthOf(7);
|
||||
expect(tags[0].name).to.eql(t('defaultTag1'));
|
||||
expect(tags[1].name).to.eql(t('defaultTag2'));
|
||||
expect(tags[2].name).to.eql(t('defaultTag3'));
|
||||
expect(tags[3].name).to.eql(t('defaultTag4'));
|
||||
expect(tags[4].name).to.eql(t('defaultTag5'));
|
||||
expect(tags[5].name).to.eql(t('defaultTag6'));
|
||||
expect(tags[6].name).to.eql(t('defaultTag7'));
|
||||
});
|
||||
});
|
||||
|
||||
context('does not provide default tags and tasks', async () => {
|
||||
it('for Android', async () => {
|
||||
api = requester(
|
||||
null,
|
||||
{'x-client': 'habitica-android'},
|
||||
);
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(0);
|
||||
expect(rewards).to.have.a.lengthOf(0);
|
||||
expect(tags).to.have.a.lengthOf(0);
|
||||
});
|
||||
|
||||
it('for iOS', async () => {
|
||||
api = requester(
|
||||
null,
|
||||
{'x-client': 'habitica-ios'},
|
||||
);
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(0);
|
||||
expect(rewards).to.have.a.lengthOf(0);
|
||||
expect(tags).to.have.a.lengthOf(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('enrolls new users in an A/B test', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||
});
|
||||
|
||||
it('includes items awarded by default when creating a new user', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.items.quests.dustbunnies).to.equal(1);
|
||||
expect(user.purchased.background.violet).to.be.ok;
|
||||
expect(user.preferences.background).to.equal('violet');
|
||||
});
|
||||
|
||||
it('requires password and confirmPassword to match', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
let confirmPassword = 'not password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a username', async () => {
|
||||
let email = `${generateRandomUserName()}@example.com`;
|
||||
let password = 'password';
|
||||
let confirmPassword = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires an email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a valid email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = 'notanemail@sdf';
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('sanitizes email params to a lowercase string before creating the user', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = 'ISANEmAiL@ExAmPle.coM';
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.email).to.equal(email.toLowerCase());
|
||||
});
|
||||
|
||||
it('fails on a habitica.com email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@habitica.com`;
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on a habitrpg.com email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@habitrpg.com`;
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a password', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let confirmPassword = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('attach to facebook user', () => {
|
||||
let user;
|
||||
let email = 'some@email.net';
|
||||
let username = 'some-username';
|
||||
let password = 'some-password';
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
it('checks onlySocialAttachLocal', async () => {
|
||||
await expect(user.post('/user/auth/local/register', {
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlySocialAttachLocal'),
|
||||
});
|
||||
});
|
||||
it('succeeds', async () => {
|
||||
await user.update({ 'auth.facebook.id': 'some-fb-id', 'auth.local': { ok: true } });
|
||||
await user.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.auth.local.email).to.eql(email);
|
||||
});
|
||||
});
|
||||
|
||||
context('login is already taken', () => {
|
||||
let username, email, api;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
|
||||
return generateUser({
|
||||
'auth.local.username': username,
|
||||
'auth.local.lowerCaseUsername': username,
|
||||
'auth.local.email': email,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects if username is already taken', async () => {
|
||||
let uniqueEmail = `${generateRandomUserName()}@exampe.com`;
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email: uniqueEmail,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('usernameTaken'),
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects if email is already taken', async () => {
|
||||
let uniqueUsername = generateRandomUserName();
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username: uniqueUsername,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('emailTaken'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('req.query.groupInvite', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(() => {
|
||||
api = requester();
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('does not crash the signup process when it\'s invalid', async () => {
|
||||
let user = await api.post('/user/auth/local/register?groupInvite=aaaaInvalid', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user._id).to.be.a('string');
|
||||
});
|
||||
|
||||
it('supports invite using req.query.groupInvite', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(), // so we can let it expire
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.parties[0].id).to.eql(group._id);
|
||||
expect(user.invitations.parties[0].name).to.eql(group.name);
|
||||
expect(user.invitations.parties[0].inviter).to.eql(groupLeader._id);
|
||||
});
|
||||
|
||||
it('awards achievement to inviter', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.achievements.invitedFriend).to.be.true;
|
||||
});
|
||||
|
||||
it('user not added to a party on expired invite', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now() - 6.912e8, // 8 days old
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.party).to.eql({});
|
||||
});
|
||||
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.guilds[0]).to.eql({
|
||||
id: group._id,
|
||||
name: group.name,
|
||||
inviter: groupLeader._id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login via api', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(() => {
|
||||
api = requester();
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('sets all site tour values to -2 (already seen)', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.flags.tour).to.not.be.empty;
|
||||
|
||||
each(user.flags.tour, (value) => {
|
||||
expect(value).to.eql(-2);
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default todos, not no other task types', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.todos).to.not.be.empty;
|
||||
expect(user.tasksOrder.dailys).to.be.empty;
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('populates user with default tags', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tags).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login with habitica-web header', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(() => {
|
||||
api = requester({}, {'x-client': 'habitica-web'});
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('sets all common tutorial flags to true', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.flags.tour).to.not.be.empty;
|
||||
|
||||
each(user.flags.tutorial.common, (value) => {
|
||||
expect(value).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default todos, habits, and rewards', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.todos).to.be.empty;
|
||||
expect(user.tasksOrder.dailys).to.be.empty;
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('populates user with default tags', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tags).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('adds the correct tags to the correct tasks', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v4';
|
||||
|
||||
const ENDPOINT = '/user/auth/verify-username';
|
||||
|
||||
describe('POST /user/auth/verify-username', async () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('successfully verifies username', async () => {
|
||||
let newUsername = 'new-username';
|
||||
let response = await user.post(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ isUsable: true });
|
||||
});
|
||||
|
||||
it('successfully verifies username with allowed characters', async () => {
|
||||
let newUsername = 'new-username_123';
|
||||
let response = await user.post(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ isUsable: true });
|
||||
});
|
||||
|
||||
context('errors', async () => {
|
||||
it('errors if username is not provided', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if username is a slur', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
});
|
||||
|
||||
it('errors if username contains a slur', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'something_TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
});
|
||||
|
||||
it('errors if username is not allowed', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'support',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueForbidden')] });
|
||||
});
|
||||
|
||||
it('errors if username is not allowed regardless of casing', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'SUppORT',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueForbidden')] });
|
||||
});
|
||||
|
||||
it('errors if username has incorrect length', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'thisisaverylongusernameover20characters',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength')] });
|
||||
});
|
||||
|
||||
it('errors if username contains invalid characters', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'Eichhörnchen',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueInvalidCharacters')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'test.name',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueInvalidCharacters')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: '🤬',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueInvalidCharacters')] });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,224 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v4';
|
||||
import {
|
||||
bcryptCompare,
|
||||
sha1MakeSalt,
|
||||
sha1Encrypt as sha1EncryptPassword,
|
||||
} from '../../../../../website/server/libs/password';
|
||||
|
||||
const ENDPOINT = '/user/auth/update-username';
|
||||
|
||||
describe('PUT /user/auth/update-username', async () => {
|
||||
let user;
|
||||
let password = 'password'; // from habitrpg/test/helpers/api-integration/v4/object-generators.js
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('successfully changes username with password', async () => {
|
||||
let newUsername = 'new-username';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
password,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(newUsername);
|
||||
});
|
||||
|
||||
it('successfully changes username without password', async () => {
|
||||
let newUsername = 'new-username-nopw';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(newUsername);
|
||||
});
|
||||
|
||||
it('successfully changes username containing number and underscore', async () => {
|
||||
let newUsername = 'new_username9';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(newUsername);
|
||||
});
|
||||
|
||||
it('sets verifiedUsername when changing username', async () => {
|
||||
user.flags.verifiedUsername = false;
|
||||
await user.sync();
|
||||
let newUsername = 'new-username-verify';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.flags.verifiedUsername).to.eql(true);
|
||||
});
|
||||
|
||||
it('converts user with SHA1 encrypted password to bcrypt encryption', async () => {
|
||||
let myNewUsername = 'my-new-username';
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
await user.update({
|
||||
'auth.local.hashed_password': sha1HashedPassword,
|
||||
'auth.local.passwordHashMethod': 'sha1',
|
||||
'auth.local.salt': salt,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
|
||||
expect(user.auth.local.salt).to.equal(salt);
|
||||
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
|
||||
|
||||
// update email
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: myNewUsername,
|
||||
password: textPassword,
|
||||
});
|
||||
expect(response).to.eql({ username: myNewUsername });
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.auth.local.username).to.eql(myNewUsername);
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('bcrypt');
|
||||
expect(user.auth.local.salt).to.be.undefined;
|
||||
expect(user.auth.local.hashed_password).not.to.equal(sha1HashedPassword);
|
||||
|
||||
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
|
||||
expect(isValidPassword).to.equal(true);
|
||||
});
|
||||
|
||||
context('errors', async () => {
|
||||
it('prevents username update if new username is already taken', async () => {
|
||||
let existingUsername = 'existing-username';
|
||||
await generateUser({'auth.local.username': existingUsername, 'auth.local.lowerCaseUsername': existingUsername });
|
||||
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: existingUsername,
|
||||
password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameTaken'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if password is wrong', async () => {
|
||||
let newUsername = 'new-username';
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
password: 'wrong-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is not provided', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is a slur', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username contains a slur', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'something_TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is not allowed', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'support',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueForbidden'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is not allowed regardless of casing', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'SUppORT',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueForbidden'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if username has incorrect length', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'thisisaverylongusernameover20characters',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueLength'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username contains invalid characters', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'Eichhörnchen',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueInvalidCharacters'),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'test.name',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueInvalidCharacters'),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: '🤬',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueInvalidCharacters'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('canDelete getter', () => {
|
||||
it('cannot delete active challenge task', () => {
|
||||
const store = generateStore();
|
||||
|
||||
|
||||
const task = {userId: 1, challenge: {id: 2}};
|
||||
expect(store.getters['tasks:canDelete'](task)).to.equal(false);
|
||||
});
|
||||
|
||||
it('can delete broken challenge task', () => {
|
||||
const store = generateStore();
|
||||
|
||||
|
||||
const task = {userId: 1, challenge: {id: 2, broken: true}};
|
||||
expect(store.getters['tasks:canDelete'](task)).to.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('getTaskClasses getter', () => {
|
||||
let store, getTaskClasses;
|
||||
|
||||
beforeEach(() => {
|
||||
store = generateStore();
|
||||
store.state.user.data = {
|
||||
preferences: {
|
||||
},
|
||||
};
|
||||
|
||||
getTaskClasses = store.getters['tasks:getTaskClasses'];
|
||||
});
|
||||
|
||||
it('returns reward edit-modal-bg class', () => {
|
||||
const task = {type: 'reward'};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-purple-modal-bg');
|
||||
});
|
||||
|
||||
it('returns worst task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: -21};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-worst-modal-bg');
|
||||
});
|
||||
|
||||
it('returns worse task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: -11};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-worse-modal-bg');
|
||||
});
|
||||
|
||||
it('returns bad task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: -6};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-bad-modal-bg');
|
||||
});
|
||||
|
||||
it('returns neutral task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 0};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-neutral-modal-bg');
|
||||
});
|
||||
|
||||
it('returns good task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 2};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-good-modal-bg');
|
||||
});
|
||||
|
||||
it('returns better task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 6};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-better-modal-bg');
|
||||
});
|
||||
|
||||
|
||||
it('returns best task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-best-modal-bg');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-text class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-text')).to.equal('task-best-modal-text');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-icon class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-icon')).to.equal('task-best-modal-icon');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-option-disabled class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-option-disabled')).to.equal('task-best-modal-option-disabled');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-control-disabled class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-habit-control-disabled')).to.equal('task-best-modal-habit-control-disabled');
|
||||
});
|
||||
|
||||
it('returns create-modal-bg class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-bg')).to.equal('task-purple-modal-bg');
|
||||
});
|
||||
|
||||
it('returns create-modal-text class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-text')).to.equal('task-purple-modal-text');
|
||||
});
|
||||
|
||||
it('returns create-modal-icon class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-icon')).to.equal('task-purple-modal-icon');
|
||||
});
|
||||
|
||||
it('returns create-modal-option-disabled class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-option-disabled')).to.equal('task-purple-modal-option-disabled');
|
||||
});
|
||||
|
||||
it('returns create-modal-habit-control-disabled class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-habit-control-disabled')).to.equal('task-purple-modal-habit-control-disabled');
|
||||
});
|
||||
|
||||
it('returns completed todo classes', () => {
|
||||
const task = {type: 'todo', value: 2, completed: true};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
bg: 'task-disabled-daily-todo-control-bg',
|
||||
checkbox: 'task-disabled-daily-todo-control-checkbox',
|
||||
inner: 'task-disabled-daily-todo-control-inner',
|
||||
content: 'task-disabled-daily-todo-control-content',
|
||||
});
|
||||
});
|
||||
|
||||
xit('returns good todo classes', () => {
|
||||
const task = {type: 'todo', value: 2};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
bg: 'task-good-control-bg',
|
||||
checkbox: 'task-good-control-checkbox',
|
||||
inner: 'task-good-control-inner-daily-todo`',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns reward classes', () => {
|
||||
const task = {type: 'reward'};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
bg: 'task-reward-control-bg',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns habit up classes', () => {
|
||||
const task = {type: 'habit', value: 2, up: true};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
up: {
|
||||
bg: 'task-good-control-bg',
|
||||
inner: 'task-good-control-inner-habit',
|
||||
},
|
||||
down: {
|
||||
bg: 'task-disabled-habit-control-bg',
|
||||
inner: 'task-disabled-habit-control-inner',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import hasClass from '../../../website/common/script/libs/hasClass';
|
||||
import { generateUser } from '../../helpers/common.helper';
|
||||
|
||||
describe('hasClass', () => {
|
||||
it('returns false for user with level below 10', () => {
|
||||
let userLvl9 = generateUser({
|
||||
'stats.lvl': 9,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
|
||||
let result = hasClass(userLvl9);
|
||||
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns false for user with class not selected', () => {
|
||||
let userClassNotSelected = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': false,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
|
||||
let result = hasClass(userClassNotSelected);
|
||||
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns false for user with classes disabled', () => {
|
||||
let userClassesDisabled = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': true,
|
||||
});
|
||||
|
||||
let result = hasClass(userClassesDisabled);
|
||||
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns true for user with class', () => {
|
||||
let userClassSelected = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
|
||||
let result = hasClass(userClassSelected);
|
||||
|
||||
expect(result).to.eql(true);
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import clearPMs from '../../../website/common/script/ops/clearPMs';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.clearPMs', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.inbox.messages = { first: 'message', second: 'message' };
|
||||
});
|
||||
|
||||
it('clears messages', () => {
|
||||
expect(user.inbox.messages).to.not.eql({});
|
||||
let [result] = clearPMs(user);
|
||||
expect(user.inbox.messages).to.eql({});
|
||||
expect(result).to.eql({});
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import deletePM from '../../../website/common/script/ops/deletePM';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.deletePM', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.inbox.messages = { first: 'message', second: 'message' };
|
||||
});
|
||||
|
||||
it('delete message', () => {
|
||||
expect(user.inbox.messages).to.not.eql({ second: 'message' });
|
||||
let [response] = deletePM(user, { params: { id: 'first' } });
|
||||
expect(user.inbox.messages).to.eql({ second: 'message' });
|
||||
expect(response).to.eql({ second: 'message' });
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,11 @@ describe('shared.ops.allocate', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if an invalid attribute is supplied', (done) => {
|
||||
@@ -28,6 +32,39 @@ describe('shared.ops.allocate', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user is below lvl 10', (done) => {
|
||||
user.stats.lvl = 9;
|
||||
try {
|
||||
allocate(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user hasn\'t selected class', (done) => {
|
||||
user.flags.classSelected = false;
|
||||
try {
|
||||
allocate(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user has disabled classes', (done) => {
|
||||
user.preferences.disableClasses = true;
|
||||
try {
|
||||
allocate(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user doesn\'t have attribute points', (done) => {
|
||||
try {
|
||||
allocate(user);
|
||||
|
||||
@@ -13,7 +13,11 @@ describe('shared.ops.allocateBulk', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if an invalid attribute is supplied', (done) => {
|
||||
@@ -43,6 +47,60 @@ describe('shared.ops.allocateBulk', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user is below lvl 10', (done) => {
|
||||
user.stats.lvl = 9;
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user hasn\'t selected class', (done) => {
|
||||
user.flags.classSelected = false;
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user has disabled classes', (done) => {
|
||||
user.preferences.disableClasses = true;
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user doesn\'t have attribute points', (done) => {
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
|
||||
+38
-20
@@ -15,15 +15,16 @@ div
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
template(v-if="isUserLoaded")
|
||||
div.resting-banner(v-if="showRestingBanner")
|
||||
div.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
span.content
|
||||
span.label {{ $t('innCheckOutBanner') }}
|
||||
span.separator |
|
||||
span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }}
|
||||
span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }}
|
||||
span.separator |
|
||||
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
|
||||
div.closepadding(@click="hideBanner()")
|
||||
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
|
||||
notifications-display
|
||||
app-menu(:class='{"restingInn": showRestingBanner}')
|
||||
app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }")
|
||||
.container-fluid
|
||||
app-header(:class='{"restingInn": showRestingBanner}')
|
||||
buyModal(
|
||||
@@ -102,7 +103,7 @@ div
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
@@ -118,20 +119,9 @@ div
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
|
||||
.restingInn {
|
||||
.navbar {
|
||||
top: 40px;
|
||||
}
|
||||
|
||||
#app-header {
|
||||
margin-top: 40px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.resting-banner {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
background-color: $blue-10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -139,11 +129,10 @@ div
|
||||
display: flex;
|
||||
|
||||
.content {
|
||||
height: 24px;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
|
||||
padding: 8px 38px 8px 8px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -160,6 +149,13 @@ div
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.content {
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: $blue-100;
|
||||
margin: 0px 15px;
|
||||
@@ -168,6 +164,7 @@ div
|
||||
.resume {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
white-space:nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -223,6 +220,7 @@ export default {
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
bannerHeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -413,7 +411,6 @@ export default {
|
||||
this.$store.watch(state => state.title, (title) => {
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
// Load external scripts after the app has been rendered
|
||||
Analytics.load();
|
||||
@@ -431,6 +428,14 @@ export default {
|
||||
|
||||
this.hideLoadingScreen();
|
||||
|
||||
window.addEventListener('resize', this.setBannerOffset);
|
||||
// Adjust the positioning of the header banners
|
||||
this.$watch('showRestingBanner', () => {
|
||||
this.$nextTick(() => {
|
||||
this.setBannerOffset();
|
||||
});
|
||||
}, {immediate: true});
|
||||
|
||||
// Adjust the timezone offset
|
||||
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) {
|
||||
this.$store.dispatch('user:set', {
|
||||
@@ -457,6 +462,7 @@ export default {
|
||||
this.$root.$off('bv::show::modal');
|
||||
this.$root.$off('buyModal::showItem');
|
||||
this.$root.$off('selectMembersModal::showItem');
|
||||
window.removeEventListener('resize', this.setBannerOffset);
|
||||
},
|
||||
mounted () {
|
||||
// Remove the index.html loading screen and now show the inapp loading
|
||||
@@ -615,10 +621,22 @@ export default {
|
||||
},
|
||||
hideBanner () {
|
||||
this.bannerHidden = true;
|
||||
this.setBannerOffset();
|
||||
},
|
||||
resumeDamage () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
setBannerOffset () {
|
||||
let contentPlacement = 0;
|
||||
if (this.showRestingBanner && this.$refs.restingBanner !== undefined) {
|
||||
contentPlacement = this.$refs.restingBanner.clientHeight;
|
||||
}
|
||||
this.bannerHeight = contentPlacement;
|
||||
let smartBanner = document.getElementsByClassName('smartbanner')[0];
|
||||
if (smartBanner !== undefined) {
|
||||
smartBanner.style.top = `${contentPlacement}px`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
.achievement-costumeContest6x {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -602px -434px;
|
||||
width: 144px;
|
||||
height: 156px;
|
||||
}
|
||||
.promo_animal_tails {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -699px;
|
||||
background-position: -284px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201809 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -699px;
|
||||
background-position: 0px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_ember_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -284px -699px;
|
||||
background-position: -142px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
@@ -60,7 +66,7 @@
|
||||
}
|
||||
.promo_seasonal_shop {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -602px -434px;
|
||||
background-position: -850px -699px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 173 KiB |
@@ -0,0 +1,56 @@
|
||||
.iconalert {
|
||||
position: relative;
|
||||
padding: $alert-padding-y $alert-padding-x;
|
||||
margin-bottom: $alert-margin-bottom;
|
||||
border-radius: 2px;
|
||||
color: white;
|
||||
font-family: Roboto;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
padding-left: 60px
|
||||
}
|
||||
|
||||
.iconalert::before {
|
||||
height:100%;
|
||||
content:' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 44px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
margin-right: $alert-padding-x;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
|
||||
.iconalert-success {
|
||||
background-color: #24cc8f;
|
||||
}
|
||||
|
||||
.iconalert-success::before {
|
||||
background-image: url(~client/assets/svg/for-css/checkbox-white.svg);
|
||||
background-size: 13px 10px;
|
||||
background-color: #1ca372;
|
||||
}
|
||||
|
||||
.iconalert-warning::before, .iconalert-error::before {
|
||||
background-image: url(~client/assets/svg/for-css/alert-white.svg);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.iconalert-warning {
|
||||
background-color: #ffa623;
|
||||
}
|
||||
|
||||
.iconalert-warning::before {
|
||||
background-color: #ee9109;
|
||||
}
|
||||
|
||||
.iconalert-error {
|
||||
background-color: #f74e52;
|
||||
}
|
||||
|
||||
.iconalert-error::before {
|
||||
background-color: #de3f3f;
|
||||
}
|
||||
@@ -34,3 +34,4 @@
|
||||
@import './progress-bar';
|
||||
@import './pin';
|
||||
@import './animals';
|
||||
@import './iconalert';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#FFF" fill-rule="evenodd" d="M0 1.994C0 .893.895 0 1.994 0h12.012C15.107 0 16 .895 16 1.994v12.012A1.995 1.995 0 0 1 14.006 16H1.994A1.995 1.995 0 0 1 0 14.006V1.994zM2 2v12h12V2H2zm5 2h2v5H7V4zm0 6h2v2H7v-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 319 B |
@@ -70,9 +70,9 @@ export default {
|
||||
facebook,
|
||||
}),
|
||||
tweet,
|
||||
achievementLink: `${BASE_URL}/social/achievement`,
|
||||
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}/social/achievement&count=none`,
|
||||
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}/social/achievement`,
|
||||
achievementLink: `${BASE_URL}`,
|
||||
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}&count=none`,
|
||||
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}`,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,20 +11,20 @@
|
||||
span {{registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}}
|
||||
.form-group(v-if='registering')
|
||||
label(for='usernameInput', v-once) {{$t('username')}}
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username')
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username', :class='{"input-valid": usernameValid, "input-invalid": usernameInvalid}')
|
||||
.form-group(v-if='!registering')
|
||||
label(for='usernameInput', v-once) {{$t('emailOrUsername')}}
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("emailOrUsername")', v-model='username')
|
||||
.form-group(v-if='registering')
|
||||
label(for='emailInput', v-once) {{$t('email')}}
|
||||
input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email')
|
||||
input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email', :class='{"input-invalid": emailInvalid, "input-valid": emailValid}')
|
||||
.form-group
|
||||
label(for='passwordInput', v-once) {{$t('password')}}
|
||||
a.float-right.forgot-password(v-once, v-if='!registering', @click='forgotPassword = true') {{$t('forgotPassword')}}
|
||||
input#passwordInput.form-control(type='password', :placeholder='$t(registering ? "passwordPlaceholder" : "password")', v-model='password')
|
||||
.form-group(v-if='registering')
|
||||
label(for='confirmPasswordInput', v-once) {{$t('confirmPassword')}}
|
||||
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm')
|
||||
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm', :class='{"input-invalid": passwordConfirmInvalid, "input-valid": passwordConfirmValid}')
|
||||
small.form-text(v-once, v-html="$t('termsAndAgreement')")
|
||||
.text-center
|
||||
.btn.btn-info(@click='register()', v-if='registering', v-once) {{$t('joinHabitica')}}
|
||||
@@ -71,12 +71,17 @@
|
||||
small.form-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-valid {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import hello from 'hellojs';
|
||||
import { setUpAxios } from 'client/libs/auth';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import facebookSquareIcon from 'assets/svg/facebook-square.svg';
|
||||
import googleIcon from 'assets/svg/google.svg';
|
||||
@@ -90,6 +95,7 @@ export default {
|
||||
email: '',
|
||||
password: '',
|
||||
passwordConfirm: '',
|
||||
usernameIssues: [],
|
||||
};
|
||||
|
||||
data.icons = Object.freeze({
|
||||
@@ -106,7 +112,50 @@ export default {
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
emailValid () {
|
||||
if (this.email.length <= 3) return false;
|
||||
return this.validateEmail(this.email);
|
||||
},
|
||||
emailInvalid () {
|
||||
return !this.emailValid;
|
||||
},
|
||||
usernameValid () {
|
||||
if (this.username.length <= 3) return false;
|
||||
return this.usernameIssues.length === 0;
|
||||
},
|
||||
usernameInvalid () {
|
||||
return !this.usernameValid;
|
||||
},
|
||||
passwordConfirmValid () {
|
||||
if (this.passwordConfirm.length <= 3) return false;
|
||||
return this.passwordConfirm === this.password;
|
||||
},
|
||||
passwordConfirmInvalid () {
|
||||
return !this.passwordConfirmValid;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
username () {
|
||||
this.validateUsername(this.username);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// eslint-disable-next-line func-names
|
||||
validateUsername: debounce(function (username) {
|
||||
if (username.length <= 3) {
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
username: this.username,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
// @TODO: Abstract hello in to action or lib
|
||||
async socialAuth (network) {
|
||||
try {
|
||||
|
||||
@@ -24,20 +24,21 @@
|
||||
.text {{registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}}
|
||||
.form-group(v-if='registering')
|
||||
label(for='usernameInput', v-once) {{$t('username')}}
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username')
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username', :class='{"input-valid": usernameValid, "input-invalid": usernameInvalid}')
|
||||
.input-error(v-for="issue in usernameIssues") {{ issue }}
|
||||
.form-group(v-if='!registering')
|
||||
label(for='usernameInput', v-once) {{$t('emailOrUsername')}}
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("emailOrUsername")', v-model='username')
|
||||
.form-group(v-if='registering')
|
||||
label(for='emailInput', v-once) {{$t('email')}}
|
||||
input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email')
|
||||
input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email', :class='{"input-invalid": emailInvalid, "input-valid": emailValid}')
|
||||
.form-group
|
||||
label(for='passwordInput', v-once) {{$t('password')}}
|
||||
a.float-right.forgot-password(v-once, v-if='!registering', @click='forgotPassword = true') {{$t('forgotPassword')}}
|
||||
input#passwordInput.form-control(type='password', :placeholder='$t(registering ? "passwordPlaceholder" : "password")', v-model='password')
|
||||
.form-group(v-if='registering')
|
||||
label(for='confirmPasswordInput', v-once) {{$t('confirmPassword')}}
|
||||
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm')
|
||||
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm', :class='{"input-invalid": passwordConfirmInvalid, "input-valid": passwordConfirmValid}')
|
||||
small.form-text(v-once, v-html="$t('termsAndAgreement')")
|
||||
.text-center
|
||||
.btn.btn-info(@click='register()', v-if='registering', v-once) {{$t('joinHabitica')}}
|
||||
@@ -200,6 +201,10 @@
|
||||
color: $white;
|
||||
}
|
||||
|
||||
#usernameInput.input-invalid {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.form-text {
|
||||
font-size: 14px;
|
||||
color: $white;
|
||||
@@ -277,11 +282,19 @@
|
||||
.forgot-password {
|
||||
color: #bda8ff !important;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
color: #fff;
|
||||
font-size: 90%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import hello from 'hellojs';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import gryphon from 'assets/svg/gryphon.svg';
|
||||
import habiticaIcon from 'assets/svg/habitica-logo.svg';
|
||||
@@ -300,6 +313,7 @@ export default {
|
||||
hasError: null,
|
||||
code: null,
|
||||
},
|
||||
usernameIssues: [],
|
||||
};
|
||||
|
||||
data.icons = Object.freeze({
|
||||
@@ -324,6 +338,30 @@ export default {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
emailValid () {
|
||||
if (this.email.length <= 3) return false;
|
||||
return this.validateEmail(this.email);
|
||||
},
|
||||
emailInvalid () {
|
||||
if (this.email.length <= 3) return false;
|
||||
return !this.emailValid;
|
||||
},
|
||||
usernameValid () {
|
||||
if (this.username.length <= 3) return false;
|
||||
return this.usernameIssues.length === 0;
|
||||
},
|
||||
usernameInvalid () {
|
||||
if (this.username.length <= 3) return false;
|
||||
return !this.usernameValid;
|
||||
},
|
||||
passwordConfirmValid () {
|
||||
if (this.passwordConfirm.length <= 3) return false;
|
||||
return this.passwordConfirm === this.password;
|
||||
},
|
||||
passwordConfirmInvalid () {
|
||||
if (this.passwordConfirm.length <= 3) return false;
|
||||
return !this.passwordConfirmValid;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
hello.init({
|
||||
@@ -357,8 +395,26 @@ export default {
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
username () {
|
||||
this.validateUsername(this.username);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// eslint-disable-next-line func-names
|
||||
validateUsername: debounce(function (username) {
|
||||
if (username.length <= 3 || !this.registering) {
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
username: this.username,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
async register () {
|
||||
// @TODO do not use alert
|
||||
if (!this.email) {
|
||||
|
||||
@@ -208,9 +208,12 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.option(v-for='option in animalItems("headAccessory")',
|
||||
:class='{active: option.active, locked: option.locked}')
|
||||
.sprite.customize-option(:class="`headAccessory_special_${option.key}`", @click='option.click')
|
||||
.gem-lock(v-if='option.locked')
|
||||
.gem-lock(v-if='option.gemLocked')
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 2
|
||||
.gold-lock(v-if='option.goldLocked')
|
||||
.svg-icon.gold(v-html='icons.gold')
|
||||
span 20
|
||||
.col-12.text-center(v-if='!animalItemsOwned("headAccessory")')
|
||||
.gem-lock
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
@@ -221,9 +224,12 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.option(v-for='option in animalItems("back")',
|
||||
:class='{active: option.active, locked: option.locked}')
|
||||
.sprite.customize-option(:class="`icon_back_special_${option.key}`", @click='option.click')
|
||||
.gem-lock(v-if='option.locked')
|
||||
.gem-lock(v-if='option.gemLocked')
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 2
|
||||
.gold-lock(v-if='option.goldLocked')
|
||||
.svg-icon.gold(v-html='icons.gold')
|
||||
span 20
|
||||
.col-12.text-center(v-if='!animalItemsOwned("back")')
|
||||
.gem-lock
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
@@ -585,20 +591,21 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
}
|
||||
}
|
||||
|
||||
.text-center .gem-lock {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1.6em;
|
||||
vertical-align: bottom;
|
||||
.text-center {
|
||||
.gem-lock, .gold-lock {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1.6em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.gem-lock {
|
||||
.gem-lock, .gold-lock {
|
||||
.svg-icon {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #24cc8f;
|
||||
font-weight: bold;
|
||||
margin-left: .5em;
|
||||
}
|
||||
@@ -609,6 +616,14 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
}
|
||||
}
|
||||
|
||||
.gem-lock span {
|
||||
color: $green-10
|
||||
}
|
||||
|
||||
.gold-lock span {
|
||||
color: $yellow-10
|
||||
}
|
||||
|
||||
.option.active {
|
||||
border-color: $purple-200;
|
||||
}
|
||||
@@ -725,7 +740,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
color: #24cc8f;
|
||||
}
|
||||
|
||||
.gem {
|
||||
.gem, .coin {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
@@ -740,13 +755,13 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gem {
|
||||
.gem, .coin {
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gem {
|
||||
.gem, .coin {
|
||||
margin: 0 .5em;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
@@ -852,6 +867,7 @@ import { mapState } from 'client/libs/store';
|
||||
import avatar from './avatar';
|
||||
import { getBackgroundShopSets } from '../../common/script/libs/shops';
|
||||
import unlock from '../../common/script/ops/unlock';
|
||||
import buy from '../../common/script/ops/buy/buy';
|
||||
import guide from 'client/mixins/guide';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import appearance from 'common/script/content/appearance';
|
||||
@@ -865,6 +881,7 @@ import skinIcon from 'assets/svg/skin.svg';
|
||||
import hairIcon from 'assets/svg/hair.svg';
|
||||
import backgroundsIcon from 'assets/svg/backgrounds.svg';
|
||||
import gem from 'assets/svg/gem.svg';
|
||||
import gold from 'assets/svg/gold.svg';
|
||||
import pin from 'assets/svg/pin.svg';
|
||||
import isPinned from 'common/script/libs/isPinned';
|
||||
|
||||
@@ -1042,6 +1059,7 @@ export default {
|
||||
backgroundsIcon,
|
||||
gem,
|
||||
pin,
|
||||
gold,
|
||||
}),
|
||||
modalPage: 1,
|
||||
activeTopPage: 'body',
|
||||
@@ -1505,6 +1523,24 @@ export default {
|
||||
alert(e.message);
|
||||
}
|
||||
},
|
||||
async buy (item) {
|
||||
const options = {
|
||||
currency: 'gold',
|
||||
key: item,
|
||||
type: 'marketGear',
|
||||
quantity: 1,
|
||||
pinType: 'marketGear',
|
||||
};
|
||||
await axios.post(`/api/v4/user/buy/${item}`, options);
|
||||
try {
|
||||
buy(this.user, {
|
||||
params: options,
|
||||
});
|
||||
this.backgroundUpdate = new Date();
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
},
|
||||
setKeys (type, _set) {
|
||||
return map(_set, (v, k) => {
|
||||
if (type === 'background') k = v.key;
|
||||
@@ -1543,7 +1579,7 @@ export default {
|
||||
|
||||
let own = true;
|
||||
this.animalItemKeys[category].forEach(key => {
|
||||
if (!this.user.items.gear.owned[`${category}_special_${key}`]) own = false;
|
||||
if (this.user.items.gear.owned[`${category}_special_${key}`] === undefined) own = false;
|
||||
});
|
||||
return own;
|
||||
},
|
||||
@@ -1554,15 +1590,22 @@ export default {
|
||||
let options = keys.map(key => {
|
||||
let newKey = `${category}_special_${key}`;
|
||||
let userPurchased = this.user.items.gear.owned[newKey];
|
||||
let locked = !userPurchased;
|
||||
|
||||
let option = {};
|
||||
option.key = key;
|
||||
option.active = this.user.preferences.costume ? this.user.items.gear.costume[category] === newKey : this.user.items.gear.equipped[category] === newKey;
|
||||
option.locked = locked;
|
||||
option.gemLocked = userPurchased === undefined;
|
||||
option.goldLocked = userPurchased === false;
|
||||
option.locked = option.gemLocked || option.goldLocked;
|
||||
option.click = () => {
|
||||
let type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||
return locked ? this.unlock(`items.gear.owned.${newKey}`) : this.equip(newKey, type);
|
||||
if (option.gemLocked) {
|
||||
return this.unlock(`items.gear.owned.${newKey}`);
|
||||
} else if (option.goldLocked) {
|
||||
return this.buy(newKey);
|
||||
} else {
|
||||
let type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||
return this.equip(newKey, type);
|
||||
}
|
||||
};
|
||||
return option;
|
||||
});
|
||||
|
||||
@@ -61,4 +61,4 @@ export default {
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<template lang="pug">
|
||||
base-notification(
|
||||
:can-remove="false",
|
||||
:has-icon="false",
|
||||
:read-after-click="false",
|
||||
:notification="{}",
|
||||
@click="action",
|
||||
)
|
||||
div.text-center(slot="content")
|
||||
div.username-notification-title {{ $t('setUsernameNotificationTitle') }}
|
||||
div {{ $t('setUsernameNotificationBody') }}
|
||||
div.current-username-container.mx-auto
|
||||
label.font-weight-bold {{ $t('currentUsername') + " " }}
|
||||
label @
|
||||
label {{ user.auth.local.username }}
|
||||
.notifications-buttons
|
||||
.btn.btn-small.btn-secondary(@click.stop="changeUsername()") {{ $t('goToSettings') }}
|
||||
</template>
|
||||
<style lang='scss'>
|
||||
@import '../../../assets/scss/colors.scss';
|
||||
|
||||
.username-notification-title {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: $purple-300;
|
||||
}
|
||||
|
||||
.current-username-container {
|
||||
border-radius: 2px;
|
||||
background-color: #f9f9f9;
|
||||
border: solid 1px #e1e0e3;
|
||||
padding: 8px 16px 8px 16px;
|
||||
display: inline-block;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.notification-buttons {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
import BaseNotification from './base';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
props: ['notification'],
|
||||
components: {
|
||||
BaseNotification,
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
},
|
||||
methods: {
|
||||
action () {
|
||||
this.$router.push({ name: 'site' });
|
||||
},
|
||||
async confirmUsername () {
|
||||
await axios.put('/api/v4/user/auth/update-username', {username: this.user.auth.local.username});
|
||||
},
|
||||
changeUsername () {
|
||||
this.$router.push({ name: 'site' });
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -93,6 +93,7 @@ import CARD_RECEIVED from './notifications/cardReceived';
|
||||
import NEW_INBOX_MESSAGE from './notifications/newInboxMessage';
|
||||
import NEW_CHAT_MESSAGE from './notifications/newChatMessage';
|
||||
import WORLD_BOSS from './notifications/worldBoss';
|
||||
import VERIFY_USERNAME from './notifications/verifyUsername';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -105,6 +106,7 @@ export default {
|
||||
UNALLOCATED_STATS_POINTS, NEW_MYSTERY_ITEMS, CARD_RECEIVED,
|
||||
NEW_INBOX_MESSAGE, NEW_CHAT_MESSAGE,
|
||||
WorldBoss: WORLD_BOSS,
|
||||
VERIFY_USERNAME,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -127,6 +129,7 @@ export default {
|
||||
'QUEST_INVITATION', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
|
||||
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
|
||||
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
|
||||
'VERIFY_USERNAME',
|
||||
],
|
||||
};
|
||||
},
|
||||
@@ -179,6 +182,16 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.user.flags.verifiedUsername !== true) {
|
||||
notifications.push({
|
||||
type: 'VERIFY_USERNAME',
|
||||
data: {
|
||||
username: this.user.auth.local.username,
|
||||
},
|
||||
id: 'custom-change-username',
|
||||
});
|
||||
}
|
||||
|
||||
const orderMap = this.notificationsOrder;
|
||||
|
||||
// Push the notifications stored in user.notifications
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
reset-modal
|
||||
delete-modal
|
||||
h1.col-12 {{ $t('settings') }}
|
||||
.col-6
|
||||
.col-sm-6
|
||||
.form-horizontal
|
||||
h5 {{ $t('language') }}
|
||||
select.form-control(:value='user.preferences.language',
|
||||
@@ -105,7 +105,7 @@
|
||||
p(v-html="$t('timezoneUTC', {utc: timezoneOffsetToUtc})")
|
||||
p(v-html="$t('timezoneInfo')")
|
||||
|
||||
.col-6
|
||||
.col-sm-6
|
||||
h2 {{ $t('registration') }}
|
||||
.panel-body
|
||||
div
|
||||
@@ -115,13 +115,9 @@
|
||||
button.btn.btn-primary.mb-2(disabled='disabled', v-if='!hasBackupAuthOption(network.key) && user.auth[network.key].id') {{ $t('registeredWithSocial', {network: network.name}) }}
|
||||
button.btn.btn-danger(@click='deleteSocialAuth(network)', v-if='hasBackupAuthOption(network.key) && user.auth[network.key].id') {{ $t('detachSocial', {network: network.name}) }}
|
||||
hr
|
||||
div(v-if='!user.auth.local.username')
|
||||
div(v-if='!user.auth.local.email')
|
||||
p {{ $t('addLocalAuth') }}
|
||||
p {{ $t('usernameLimitations') }}
|
||||
.form(name='localAuth', novalidate)
|
||||
//-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted') {{ $t('fillAll') }}
|
||||
.form-group
|
||||
input.form-control(type='text', :placeholder="$t('username')", v-model='localAuth.username', required)
|
||||
.form-group
|
||||
input.form-control(type='text', :placeholder="$t('email')", v-model='localAuth.email', required)
|
||||
.form-group
|
||||
@@ -130,37 +126,37 @@
|
||||
input.form-control(type='password', :placeholder="$t('confirmPass')", v-model='localAuth.confirmPassword', required)
|
||||
button.btn.btn-primary(type='submit', @click='addLocalAuth()') {{ $t('submit') }}
|
||||
|
||||
.usersettings(v-if='user.auth.local.username')
|
||||
p {{ $t('username') }}
|
||||
|: {{user.auth.local.username}}
|
||||
p
|
||||
small.muted
|
||||
| {{ $t('loginNameDescription') }}
|
||||
p {{ $t('email') }}
|
||||
|: {{user.auth.local.email}}
|
||||
hr
|
||||
.usersettings
|
||||
h5 {{ $t('changeDisplayName') }}
|
||||
.form(name='changeDisplayName', novalidate)
|
||||
.form-group
|
||||
input#changeDisplayname.form-control(type='text', :placeholder="$t('newDisplayName')", v-model='temporaryDisplayName')
|
||||
button.btn.btn-primary(type='submit', @click='changeDisplayName(temporaryDisplayName)') {{ $t('submit') }}
|
||||
|
||||
h5 {{ $t('changeUsername') }}
|
||||
.form(v-if='user.auth.local', name='changeUsername', novalidate)
|
||||
//-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted') {{ $t('fillAll') }}
|
||||
.form(name='changeUsername', novalidate)
|
||||
.iconalert.iconalert-success(v-if='verifiedUsername') {{ $t('usernameVerifiedConfirmation', {'username': user.auth.local.username}) }}
|
||||
.iconalert.iconalert-warning(v-else)
|
||||
div.align-middle
|
||||
span {{ $t('usernameNotVerified') }}
|
||||
button.btn.btn-secondary.btn-small.float-right(@click='changeUser("username", {username: user.auth.local.username})') {{ $t('confirmUsername') }}
|
||||
.form-group
|
||||
input.form-control(type='text', :placeholder="$t('newUsername')", v-model='usernameUpdates.username')
|
||||
input#changeUsername.form-control(type='text', :placeholder="$t('newUsername')", v-model='usernameUpdates.username', :class='{"is-invalid input-invalid": usernameInvalid}')
|
||||
.input-error(v-for="issue in usernameIssues") {{ issue }}
|
||||
small.form-text.text-muted {{ $t('changeUsernameDisclaimer') }}
|
||||
button.btn.btn-primary(type='submit', @click='changeUser("username", usernameUpdates)', :disabled='usernameCanSubmit') {{ $t('submit') }}
|
||||
h5(v-if='user.auth.local.email') {{ $t('changeEmail') }}
|
||||
.form(v-if='user.auth.local.email', name='changeEmail', novalidate)
|
||||
.form-group
|
||||
input.form-control(type='password', :placeholder="$t('password')", v-model='usernameUpdates.password')
|
||||
button.btn.btn-primary(type='submit', @click='changeUser("username", usernameUpdates)') {{ $t('submit') }}
|
||||
|
||||
h5 {{ $t('changeEmail') }}
|
||||
.form(v-if='user.auth.local', name='changeEmail', novalidate)
|
||||
.form-group
|
||||
input.form-control(type='text', :placeholder="$t('newEmail')", v-model='emailUpdates.newEmail')
|
||||
input#changeEmail.form-control(type='text', :placeholder="$t('newEmail')", v-model='emailUpdates.newEmail')
|
||||
.form-group
|
||||
input.form-control(type='password', :placeholder="$t('password')", v-model='emailUpdates.password')
|
||||
button.btn.btn-primary(type='submit', @click='changeUser("email", emailUpdates)') {{ $t('submit') }}
|
||||
|
||||
h5 {{ $t('changePass') }}
|
||||
.form(v-if='user.auth.local', name='changePassword', novalidate)
|
||||
h5(v-if='user.auth.local.email') {{ $t('changePass') }}
|
||||
.form(v-if='user.auth.local.email', name='changePassword', novalidate)
|
||||
.form-group
|
||||
input.form-control(type='password', :placeholder="$t('oldPass')", v-model='passwordUpdates.password')
|
||||
input#changePassword.form-control(type='password', :placeholder="$t('oldPass')", v-model='passwordUpdates.password')
|
||||
.form-group
|
||||
input.form-control(type='password', :placeholder="$t('newPass')", v-model='passwordUpdates.newPassword')
|
||||
.form-group
|
||||
@@ -177,10 +173,33 @@
|
||||
popover-trigger='mouseenter', v-b-popover.hover.auto="$t('deleteAccPop')") {{ $t('deleteAccount') }}
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
input {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.usersettings h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.iconalert > div > span {
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.iconalert > div:after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
color: $red-50;
|
||||
font-size: 90%;
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -188,7 +207,7 @@ import hello from 'hellojs';
|
||||
import moment from 'moment';
|
||||
import axios from 'axios';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import restoreModal from './restoreModal';
|
||||
import resetModal from './resetModal';
|
||||
import deleteModal from './deleteModal';
|
||||
@@ -224,7 +243,8 @@ export default {
|
||||
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
|
||||
dayStartOptions,
|
||||
newDayStart: 0,
|
||||
usernameUpdates: {},
|
||||
temporaryDisplayName: '',
|
||||
usernameUpdates: {username: ''},
|
||||
emailUpdates: {},
|
||||
passwordUpdates: {},
|
||||
localAuth: {
|
||||
@@ -233,6 +253,7 @@ export default {
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
usernameIssues: [],
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
@@ -240,12 +261,25 @@ export default {
|
||||
// @TODO: We may need to request the party here
|
||||
this.party = this.$store.state.party;
|
||||
this.newDayStart = this.user.preferences.dayStart;
|
||||
this.usernameUpdates.username = this.user.auth.local.username || null;
|
||||
this.temporaryDisplayName = this.user.profile.name;
|
||||
this.emailUpdates.newEmail = this.user.auth.local.email || null;
|
||||
hello.init({
|
||||
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line no-process-env
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line no-process-env
|
||||
}, {
|
||||
redirect_uri: '', // eslint-disable-line
|
||||
});
|
||||
|
||||
const focusID = this.$route.query.focus;
|
||||
if (focusID !== undefined && focusID !== null) {
|
||||
this.$nextTick(() => {
|
||||
const element = document.getElementById(focusID);
|
||||
if (element !== undefined && element !== null) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
@@ -275,8 +309,47 @@ export default {
|
||||
hasClass () {
|
||||
return this.$store.getters['members:hasClass'](this.user);
|
||||
},
|
||||
verifiedUsername () {
|
||||
return this.user.flags.verifiedUsername;
|
||||
},
|
||||
usernameValid () {
|
||||
if (this.usernameUpdates.username.length <= 1) return false;
|
||||
return this.usernameIssues.length === 0;
|
||||
},
|
||||
usernameInvalid () {
|
||||
if (this.usernameUpdates.username.length <= 1) return false;
|
||||
return !this.usernameValid;
|
||||
},
|
||||
usernameCanSubmit () {
|
||||
if (this.usernameUpdates.username.length <= 1) return true;
|
||||
return !this.usernameValid;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
usernameUpdates: {
|
||||
handler () {
|
||||
this.validateUsername(this.usernameUpdates.username);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// eslint-disable-next-line func-names
|
||||
validateUsername: debounce(function (username) {
|
||||
if (username.length <= 1 || username === this.user.auth.local.username) {
|
||||
this.usernameIssues = [];
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
username,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
set (preferenceType, subtype) {
|
||||
let settings = {};
|
||||
if (!subtype) {
|
||||
@@ -349,8 +422,18 @@ export default {
|
||||
},
|
||||
async changeUser (attribute, updates) {
|
||||
await axios.put(`/api/v4/user/auth/update-${attribute}`, updates);
|
||||
alert(this.$t(`${attribute}Success`));
|
||||
this.user[attribute] = updates[attribute];
|
||||
if (attribute === 'username') {
|
||||
this.user.auth.local.username = updates[attribute];
|
||||
this.user.flags.verifiedUsername = true;
|
||||
} else if (attribute === 'email') {
|
||||
this.user.auth.local.email = updates[attribute];
|
||||
}
|
||||
},
|
||||
async changeDisplayName (newName) {
|
||||
await axios.put('/api/v4/user/', {'profile.name': newName});
|
||||
alert(this.$t('displayNameSuccess'));
|
||||
this.user.profile.name = newName;
|
||||
this.temporaryDisplayName = newName;
|
||||
},
|
||||
openRestoreModal () {
|
||||
this.$root.$emit('bv::show::modal', 'restore');
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
span {{$t('or')}}
|
||||
.form(@keyup.enter="register()")
|
||||
p.form-text {{$t('usernameLimitations')}}
|
||||
input.form-control(type='text', placeholder='Login Name', v-model='username', :class='{"input-valid": username.length > 3}')
|
||||
input#usernameInput.form-control(type='text', placeholder='Login Name', v-model='username', :class='{"input-valid": usernameValid, "input-invalid": usernameInvalid}')
|
||||
.input-error(v-for="issue in usernameIssues") {{ issue }}
|
||||
input.form-control(type='email', placeholder='Email', v-model='email', :class='{"input-invalid": emailInvalid, "input-valid": emailValid}')
|
||||
input.form-control(type='password', placeholder='Password', v-model='password', :class='{"input-valid": password.length > 3}')
|
||||
input.form-control(type='password', placeholder='Confirm Password', v-model='passwordConfirm', :class='{"input-invalid": passwordConfirmInvalid, "input-valid": passwordConfirmValid}')
|
||||
@@ -293,6 +294,10 @@
|
||||
transition: border .5s, color .5s;
|
||||
}
|
||||
|
||||
#usernameInput.input-invalid {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.input-valid {
|
||||
color: #fff;
|
||||
}
|
||||
@@ -525,10 +530,19 @@
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.input-error {
|
||||
color: #fff;
|
||||
font-size: 90%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import hello from 'hellojs';
|
||||
import debounce from 'lodash/debounce';
|
||||
import googlePlay from 'assets/images/home/google-play-badge.svg';
|
||||
import iosAppStore from 'assets/images/home/ios-app-store.svg';
|
||||
import iphones from 'assets/images/home/iphones.svg';
|
||||
@@ -575,6 +589,7 @@
|
||||
password: '',
|
||||
passwordConfirm: '',
|
||||
email: '',
|
||||
usernameIssues: [],
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
@@ -600,6 +615,14 @@
|
||||
if (this.email.length <= 3) return false;
|
||||
return !this.validateEmail(this.email);
|
||||
},
|
||||
usernameValid () {
|
||||
if (this.username.length <= 3) return false;
|
||||
return this.usernameIssues.length === 0;
|
||||
},
|
||||
usernameInvalid () {
|
||||
if (this.username.length <= 3) return false;
|
||||
return !this.usernameValid;
|
||||
},
|
||||
passwordConfirmValid () {
|
||||
if (this.passwordConfirm.length <= 3) return false;
|
||||
return this.passwordConfirm === this.password;
|
||||
@@ -609,11 +632,31 @@
|
||||
return this.passwordConfirm !== this.password;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
username () {
|
||||
this.validateUsername(this.username);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
validateEmail (email) {
|
||||
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
return re.test(email);
|
||||
},
|
||||
// eslint-disable-next-line func-names
|
||||
validateUsername: debounce(function (username) {
|
||||
if (username.length <= 3) {
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
username: this.username,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
// @TODO this is totally duplicate from the registerLogin component
|
||||
async register () {
|
||||
let groupInvite = '';
|
||||
|
||||
@@ -282,7 +282,7 @@
|
||||
color: $gray-50;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: -3px;
|
||||
min-height: 0px;
|
||||
width: 100%;
|
||||
margin-left: 8px;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
.input-group
|
||||
.input-group-prepend.input-group-icon.align-items-center
|
||||
.svg-icon.gold(v-html="icons.gold")
|
||||
input.form-control(type="number", v-model="task.value", required, placeholder="1.0", step="0.01", min="0")
|
||||
input.form-control(type="number", v-model="task.value", required, placeholder="Enter a Value", step="0.01", min="0")
|
||||
|
||||
.option.mt-0(v-if="checklistEnabled")
|
||||
label(v-once) {{ $t('checklist') }}
|
||||
|
||||
@@ -287,7 +287,7 @@ export default {
|
||||
const convos = [];
|
||||
for (let key in inboxGroup) {
|
||||
const convoSorted = sortBy(inboxGroup[key], [(o) => {
|
||||
return o.timestamp;
|
||||
return (new Date(o.timestamp)).getTime();
|
||||
}]);
|
||||
|
||||
// Fix poor inbox chat models
|
||||
|
||||
@@ -46,6 +46,15 @@ export async function login (store, params) {
|
||||
localStorage.setItem(LOCALSTORAGE_AUTH_KEY, userLocalData);
|
||||
}
|
||||
|
||||
export async function verifyUsername (store, params) {
|
||||
let url = '/api/v4/user/auth/verify-username';
|
||||
let result = await axios.post(url, {
|
||||
username: params.username,
|
||||
});
|
||||
|
||||
return result.data.data;
|
||||
}
|
||||
|
||||
export async function socialAuth (store, params) {
|
||||
let url = '/api/v4/user/auth/social';
|
||||
let result = await axios.post(url, {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import memberHasClass from 'common/script/libs/hasClass';
|
||||
|
||||
export function isBuffed () {
|
||||
return (member) => {
|
||||
const buffs = member.stats.buffs;
|
||||
@@ -6,7 +8,5 @@ export function isBuffed () {
|
||||
}
|
||||
|
||||
export function hasClass () {
|
||||
return (member) => {
|
||||
return member.stats.lvl >= 10 && !member.preferences.disableClasses && member.flags.classSelected;
|
||||
};
|
||||
return memberHasClass;
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Други",
|
||||
"fullName": "Пълно име",
|
||||
"displayName": "Екранно име",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Снимка",
|
||||
"displayBlurb": "За Вас",
|
||||
"displayBlurbPlaceholder": "Моля, представете се",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "Използвахте мана",
|
||||
"lostHealth": "Загубихте здраве",
|
||||
"lostExperience": "Загубихте опит",
|
||||
"displayNameDescription1": "Това се появява в съобщенията, които пишете в кръчмата, гилдиите и чата на групата, както и се показва върху героя Ви. За да го промените, щракнете бутона „Редактиране“ по-горе. Ако искате да промените потребителското си име за влизане в системата, отидете в",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Настройки->Уеб сайт",
|
||||
"displayNameDescription3": "и погледнете в раздела за регистрация.",
|
||||
"unequipBattleGear": "Разекипиране на бойното снаряжение",
|
||||
@@ -204,7 +206,7 @@
|
||||
"hideQuickAllocation": "Скриване на разпределението на показателните точки",
|
||||
"quickAllocationLevelPopover": "Всяко ниво Ви дава една точка, която можете да разпределите на показател по свой избор. Можете да го направите ръчно или да оставите играта да реши вместо Вас, използвайки една възможностите за автоматично разпределяне, които можете да намерите в Потребителската иконка > Показатели.",
|
||||
"notEnoughAttrPoints": "Нямате достатъчно показателни точки.",
|
||||
"classNotSelected": "You must select Class before you can assign Stat Points.",
|
||||
"classNotSelected": "Трябва първо да изберете клас, преди да можете да разпределяте показателни точки.",
|
||||
"style": "Стил",
|
||||
"facialhair": "Лице",
|
||||
"photo": "Снимка",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Отключвайте нови средства за мотивация като любимци, случайни награди, заклинания и други!",
|
||||
"unlockHeadline": "Поддържайки продуктивността си, Вие отключвате ново съдържание!",
|
||||
"useUUID": "Използвайте идентификатор UUID / жетон за ППИ (за потребители на Фейсбук)",
|
||||
"username": "Потребителско име",
|
||||
"emailOrUsername": "Е-поща или потребителско име (чувствителност към регистъра)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Вижте видеоклиповете",
|
||||
"work": "Работа",
|
||||
"zelahQuote": "Хабитика може да ме накара да си лягам навреме, тъй като знам, че ще спечеля точки за това; или ще изгубя здраве, ако закъснея!",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Липсват заглавки за удостоверяване.",
|
||||
"missingAuthParams": "Липсват параметри за удостоверяване.",
|
||||
"missingUsernameEmail": "Липсва потребителско име или е-поща.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Липсва е-поща.",
|
||||
"missingUsername": "Липсва потребителско име.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Липсва парола.",
|
||||
"missingNewPassword": "Липсва нова парола.",
|
||||
"invalidEmailDomain": "Не можете да се регистрирате, използвайки е-поща от следните сървъри: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Тази е-поща вече се използва от съществуващ профил.",
|
||||
"newEmailRequired": "Липсва нов адрес на е-поща.",
|
||||
"usernameTime": "Време е да си създадете потребителско име!",
|
||||
"usernameInfo": "Екранното Ви име не е променено, но старото Ви име за вписване вече ще бъде публичното Ви потребителско име. Това потребителско име ще се използва за покани, @споменавания в чата, и за изпращане на съобщения.<br><br>Ако искате да научите повече за тази промяна, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>посетете уикито ни</a>.",
|
||||
"usernameInfo": "Екранното Ви име не е променено, но старото Ви име за вписване вече ще бъде публичното Ви потребителско име. Това потребителско име ще се използва за покани, @споменавания в чата, и за изпращане на съобщения.<br><br>Ако искате да научите повече за тази промяна, посетете страницата за <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>имената на играчите</a> в уикито ни.",
|
||||
"usernameTOSRequirements": "Потребителските имена трябва са съобразени с Условията за ползване и Обществените правила. Ако преди това не сте имали име за вписване, то потребителското Ви име е създадено автоматично.",
|
||||
"usernameTaken": "Потребителското име е заето.",
|
||||
"usernameWrongLength": "Потребителското име трябва да бъде с дължина между 1 и 20 знака.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Подновяване на паролата в Хабитика",
|
||||
"passwordResetEmailText": "Ако сте заявили нулиране на паролата си за <%= username %> в Хабитика, отидете на <%= passwordResetLink %>, за да зададете нова. Тази връзка ще загуби давност след 24 часа. Ако не сте заявили нулиране на паролата си, не обръщайте внимание на това писмо.",
|
||||
"passwordResetEmailHtml": "Ако сте заявили нулиране на паролата си за <strong><%= username %></strong> в Хабитика, <a href=\"<%= passwordResetLink %>\">натиснете тук</a>, за да зададете нова. Тази връзка ще загуби давност след 24 часа.<br/><br>Ако не сте заявили нулиране на паролата си, не обръщайте внимание на това писмо.",
|
||||
"invalidLoginCredentialsLong": "Опа, потребителското име/е-пощата или паролата е грешна.\n— Уверете се, че всичко е изписано правилно. Потребителското име и паролата са чувствителни към регистъра;\n— Може да сте се вписали чрез Фейсбук или Гугъл, а не чрез е-поща. Проверете това, като опитате да влезете чрез Фейсбук или Гугъл;\n— Ако сте забравили паролата си, натиснете „Забравена парола“.",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "Няма профил, който използва тези данни за вход.",
|
||||
"accountSuspended": "Този акаунт, с потребителски идентификатор „<%= userId %>“, е блокиран за нарушаване на [Обществените правила](https://habitica.com/static/community-guidelines) или [Условията за ползване](https://habitica.com/static/terms). За повече подробности, или ако искате да помолите за отблокиране, моля, пишете на управителя за общността на адрес <%= communityManagerEmail %> или помолете свой родител или настойник да пише. Моля, копирайте потребителския си идентификатор в е-писмото, и напишете името на профила си.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Достъпът до акаунта е преустановен",
|
||||
"unsupportedNetwork": "Тази мрежа не се поддържа в момента.",
|
||||
"cantDetachSocial": "Профилът няма друг начин за удостоверяване, така че този начин за влизане не може да бъде премахнат.",
|
||||
|
||||
@@ -1104,7 +1104,7 @@
|
||||
"headMystery201807Notes": "Здравите люспи на този шлем ще Ви защитят от всеки морски враг. Не променя показателите. Предмет за абонати: юли 2018 г.",
|
||||
"headMystery201808Text": "Качулка на дракон от лава",
|
||||
"headMystery201808Notes": "Светещите рога на тази качулка ще Ви осветят пътя през подземните пещери. Не променя показателите. Предмет за абонати: август 2018 г.",
|
||||
"headMystery201809Text": "Crown of Autumn Flowers",
|
||||
"headMystery201809Text": "Корона от есенни цветя",
|
||||
"headMystery201809Notes": "Последните цветя на топлите есенни дни напомнят за красотата на този сезон. Не променя показателите. Предмет за абонати: септември 2018 г.",
|
||||
"headMystery301404Text": "Украсен цилиндър",
|
||||
"headMystery301404Notes": "Украсен цилиндър за най-изтънчените и високопоставени членове на обществото. Не променя показателите. Предмет за абонати: януари 3015 г.",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"innText": "Вие почивате в странноприемницата! Докато сте вътре, ежедневните Ви задачи няма да Ви нараняват в края на деня, но ще продължат да се опресняват всеки ден. Внимавайте: ако участвате в мисия срещу главатар, той ще Ви наранява, когато членовете на групата Ви не изпълняват ежедневните си задачи, освен ако и те не са в странноприемницата! Освен това, докато не напуснете странноприемницата, Вашите щети срещу главатаря няма да бъдат прилагани, както и няма да получите събраните си предмети.",
|
||||
"innTextBroken": "Вие почивате в странноприемницата, предполагам… Докато сте вътре, ежедневните Ви задачи няма да Ви нараняват в края на деня, но ще продължат да се опресняват всеки ден… Ако участвате в мисия срещу главатар, той ще Ви наранява, когато членовете на групата Ви не изпълняват ежедневните си задачи… освен ако и те не са в странноприемницата! Освен това, докато не напуснете странноприемницата, Вашите щети срещу главатаря няма да бъдат прилагани, както и няма да получите събраните си предмети… толкова съм уморен…",
|
||||
"innCheckOutBanner": "В момента си почивате в странноприемницата. Докато сте тук ежедневните Ви задачи няма да Ви нанасят щети и няма да напредвате в мисиите си.",
|
||||
"innCheckOutBannerShort": "You are checked into the Inn.",
|
||||
"innCheckOutBannerShort": "Вие си почивате в странноприемницата.",
|
||||
"resumeDamage": "Продължаване на щетите",
|
||||
"helpfulLinks": "Полезни връзки",
|
||||
"communityGuidelinesLink": "Обществени правила",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "Нямате достатъчно диаманти!",
|
||||
"messageAuthPasswordMustMatch": ":password и :confirmPassword не съвпадат",
|
||||
"messageAuthCredentialsRequired": ":username, :email, :password и :confirmPassword са задължителни",
|
||||
"messageAuthUsernameTaken": "Потребителското име е заето",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "Е-пощата вече се използва",
|
||||
"messageAuthNoUserFound": "Потребителят не е намерен.",
|
||||
"messageAuthMustBeLoggedIn": "Трябва да сте влезли в системата.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Разни",
|
||||
"showHeader": "Показване на горната част",
|
||||
"changePass": "Промяна на паролата",
|
||||
"changeUsername": "Промяна на потребителското име",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Промяна на е-пощата",
|
||||
"newEmail": "Нов адрес на е-поща",
|
||||
"oldPass": "Стара парола",
|
||||
"newPass": "Нова парола",
|
||||
"confirmPass": "Повторете новата парола",
|
||||
"newUsername": "Ново потребителско име",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Опасна зона",
|
||||
"resetText1": "ВНИМАНИЕ! Това нулира части от профила Ви. Това е силно непрепоръчително, но някои хора го смятат за полезно в началото, след като са използвали уеб сайта известно време.",
|
||||
"resetText2": "Ще загубите всичките си нива, злато и точки опит. Всички задачи (освен тези от предизвикателства) ще бъдат изтрити завинаги и ще загубите цялата им история. Ще загубите всичката си екипировка, но ще можете да си купите отново всичко, включително предметите от ограничени серии и тайнствените предмети за абонати, които притежавате в момента (ще трябва да използвате правилния клас, за да закупите отново класово-специфичната екипировка). Ще запазите текущия си клас, както и любимците и превозите си. Вместо това, може да предпочетете да използвате Кълбото на прераждането, което е много по-безопасно и ще запази задачите и екипировката Ви.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "Кодът за нулиране на паролата е неправилен или с изтекла давност.",
|
||||
"passwordChangeSuccess": "Паролата Ви беше успешно променена с новата, която избрахте. Вече можете да я използвате, за да влезете в профила си.",
|
||||
"passwordSuccess": "Паролата е променена успешно",
|
||||
"usernameSuccess": "Потребителското име е променено успешно",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Е-пощата е променена успешно",
|
||||
"detachSocial": "Премахване на връзката с <%= network %>",
|
||||
"detachedSocial": "Връзката с <%= network %> беше премахната успешно от профила Ви.",
|
||||
"addedLocalAuth": "Местното удостоверяване беше добавено успешно",
|
||||
"data": "Данни",
|
||||
"exportData": "Изнасяне на данни",
|
||||
"usernameOrEmail": "Потребителско име или е-поща",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Е-поща",
|
||||
"registerWithSocial": "Регистриране чрез <%= network %>",
|
||||
"registeredWithSocial": "Вие сте се регистрирали чрез <%= network %>",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Показване на бутона за канене в групата, когато в нея има 1 член.",
|
||||
"saveCustomDayStart": "Запазване",
|
||||
"registration": "Регистрация",
|
||||
"addLocalAuth": "Добавяне на местно удостоверяване:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Създаване на кодове",
|
||||
"generate": "Създаване",
|
||||
"getCodes": "Получаване на кодове",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Хабитика използва часовия пояс, зададен на Вашия компютър, който е: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "Ако този часови пояс е грешен, първо, презаредете страницата чрез бутона за презареждане или опресняване на браузъра си, за да е сигурно, че Хабитика разполага с най-актуалните данни. Ако все още има грешка, настройте часовия пояс на компютъра си и след това презаредете тази страница отново.<br><br> <strong>Ако използвате Хабитика на други компютри или мобилни устройства, часовият пояс трябва да бъде еднакъв на всички тях.</strong> Ако ежедневните Ви задачи се подновяват в грешно време, повторете тази проверка на всичките си останали компютри и чрез браузъра на всички свои мобилни устройства.",
|
||||
"push": "Известия",
|
||||
"about": "Относно"
|
||||
"about": "Относно",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Další",
|
||||
"fullName": "Celé jméno",
|
||||
"displayName": "Zobrazené jméno",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Fotografie",
|
||||
"displayBlurb": "Něco o mně",
|
||||
"displayBlurbPlaceholder": "Prosíme, představ se",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "Utratil jsi několik many",
|
||||
"lostHealth": "Ztratil jsi několik zdraví",
|
||||
"lostExperience": "Ztratil jsi několik zkušeností",
|
||||
"displayNameDescription1": "Toto se zobrazí ve zprávách, které uveřejníš v Krčmě, ceších, a chatu v družině, spolu s tvým avatarem. Jestli to chceš změnit, klikni na tlačítko Editovat. Jestli chceš ale změnot svoje přihlašovací jméno, jdi na",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Nastavení -> Stránka",
|
||||
"displayNameDescription3": "sekci Registrace, tam to najdeš.",
|
||||
"unequipBattleGear": "Odebrat válečnou zbroj",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Odemkni novou motivaci, jako je sbírání mazlíčků, náhodné odměny, sesílání kouzel a mnohem víc!",
|
||||
"unlockHeadline": "Čím jsi produktivnější, tím více obsahu odemkneš!",
|
||||
"useUUID": "Použij UUID / API Token (pro uživatele Facebooku)",
|
||||
"username": "Login Name",
|
||||
"emailOrUsername": "Email or Login Name (case-sensitive)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Podívej se na videa",
|
||||
"work": "Práce",
|
||||
"zelahQuote": "[Program Habitica] mi pomáhá rozhodnout se, jestli jít do postele a získat za to body, nebo zůstat vzhůru a přijít o zdraví!",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Chybějící ověřovací hlavičky.",
|
||||
"missingAuthParams": "Chybějící ověřovací parametry.",
|
||||
"missingUsernameEmail": "Missing Login Name or email.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Chybějící email.",
|
||||
"missingUsername": "Missing Login Name.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Chybějící heslo.",
|
||||
"missingNewPassword": "Chybějící nové heslo.",
|
||||
"invalidEmailDomain": "Nemůžeš se zaregistrovat e-mailem z následujících domén: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "E-mailová adresa je již použita.",
|
||||
"newEmailRequired": "Chybějící e-mailová adresa.",
|
||||
"usernameTime": "Je čas nastavit si uživatelské jméno!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Obnovení hesla pro Habitica",
|
||||
"passwordResetEmailText": "If you requested a password reset for <%= username %> on Habitica, head to <%= passwordResetLink %> to set a new one. The link will expire after 24 hours. If you haven't requested a password reset, please ignore this email.",
|
||||
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / login name or password is incorrect.\n- Make sure they are typed correctly. Your login name and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "There is no account that uses those credentials.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your Profile Name.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Account has been suspended",
|
||||
"unsupportedNetwork": "Tato síť není momentálně dostupná.",
|
||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "Nedostatek drahokamů!",
|
||||
"messageAuthPasswordMustMatch": ":password a :confirmPassword se neshodují",
|
||||
"messageAuthCredentialsRequired": "Je vyžadováno :username, :email, :password, :confirmPassword ",
|
||||
"messageAuthUsernameTaken": "Přihlašovací jméno už je zabrané.",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "Email se již používá",
|
||||
"messageAuthNoUserFound": "Uživatel nenalezen.",
|
||||
"messageAuthMustBeLoggedIn": "Musíš být přihlášen.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Ostatní",
|
||||
"showHeader": "Zobrazit horní info panel",
|
||||
"changePass": "Změnit heslo",
|
||||
"changeUsername": "Změnit přihlašovací jméno",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Změnit emailovou adresu",
|
||||
"newEmail": "Nová emailová adresa",
|
||||
"oldPass": "Staré heslo",
|
||||
"newPass": "Nové heslo",
|
||||
"confirmPass": "Potvrď nové heslo",
|
||||
"newUsername": "Nové přihlašovací jméno",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Nebezpečná zóna",
|
||||
"resetText1": "POZOR! Tímto resetujete mnoho částí svého účtu. Silně nedoporučujeme tuto možnost používat, ale vyhovuje to některým uživatelům, kteří si se stránkou na začátku trochu hrají.",
|
||||
"resetText2": "You will lose all your levels, Gold, and Experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "The supplied password reset code is invalid or has expired.",
|
||||
"passwordChangeSuccess": "Tvé heslo bylo úspěšně změněno. Můžeš ho použít pro přihlášení ke svému účtu. ",
|
||||
"passwordSuccess": "Heslo úspěšně změněno",
|
||||
"usernameSuccess": "Přihlašovací jméno úspěšně změněno",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Email úspěšně změněn",
|
||||
"detachSocial": "Odregistruj <%= network %>",
|
||||
"detachedSocial": "<%= network %>ověření bylo úspěšně odebráno z tvého účtu",
|
||||
"addedLocalAuth": "Lokální ověření úspěšně přidáno",
|
||||
"data": "Data",
|
||||
"exportData": "Export dat",
|
||||
"usernameOrEmail": "Přihlašovací jméno nebo email",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Email",
|
||||
"registerWithSocial": "Registrovaný přes <%= network %>",
|
||||
"registeredWithSocial": "Registrovaný přes <%= network %>",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Zobrazit tlačítko \"Pozvat do družiny\", když má družina 1 člena.",
|
||||
"saveCustomDayStart": "Uložit vlastní začátek dne",
|
||||
"registration": "Registrace",
|
||||
"addLocalAuth": "Přidat lokální ověření:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Generovat kódy",
|
||||
"generate": "Generovat",
|
||||
"getCodes": "Získat kódy",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Habitica používá časové pásmo nastavené na tvém PC, což je : <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.<br><br> <strong>If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all.</strong> If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices.",
|
||||
"push": "Push",
|
||||
"about": "About"
|
||||
"about": "About",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Andet",
|
||||
"fullName": "Fuldt navn",
|
||||
"displayName": "Skærmnavn",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Billede",
|
||||
"displayBlurb": "Kort tekst",
|
||||
"displayBlurbPlaceholder": "Introducér dig selv",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "Du har brugt noget Mana",
|
||||
"lostHealth": "Du har mistet noget Liv",
|
||||
"lostExperience": "Du har mistet noget Erfaring",
|
||||
"displayNameDescription1": "Dette er, hvad der ses i beskeder, du skriver i Værtshuset, klaner og gruppechat, og også hvad der er vist på din avatar. For at ændre det, klik Rediger ovenfor. Hvis du i stedet ønsker at ændre dit login navn, gå til",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Indstillinger->Side",
|
||||
"displayNameDescription3": "og se Registreringssektionen.",
|
||||
"unequipBattleGear": "Fjern Kamprustning",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Lås op for nye motivationer, som at samle på kæledyr, tilfældige belønninger, kaste fortryllelser og meget mere!",
|
||||
"unlockHeadline": "Så længe du er produktiv, åbner du for nyt indhold!",
|
||||
"useUUID": "Brug dit Unikke Bruger-ID/ API Nøgle (for Facebook brugere)",
|
||||
"username": "Loginnavn",
|
||||
"emailOrUsername": "Email or Login Name (case-sensitive)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Se videoer",
|
||||
"work": "Arbejde",
|
||||
"zelahQuote": "Med [Habitica] bliver jeg lokket til at komme i seng i ordentlig tid. Jeg vil gerne have de ekstra XP for at komme tidligt i seng, og undgå at miste liv på grund af en sen aften!",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Godkendelses-headers mangler.",
|
||||
"missingAuthParams": "Manglende godkendelsesparametre.",
|
||||
"missingUsernameEmail": "Manglende loginnavn eller email.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Manglende email.",
|
||||
"missingUsername": "Manglende loginnavn.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Manglende kodeord.",
|
||||
"missingNewPassword": "Manglende nyt kodeord.",
|
||||
"invalidEmailDomain": "Du kan ikke registrere med emails med følgende domæner: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "E-mailadressen er allerede brugt til en konto.",
|
||||
"newEmailRequired": "Manglende ny e-mailadresse.",
|
||||
"usernameTime": "It's time to set your username!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Nulstilling af kodeord til Habitica",
|
||||
"passwordResetEmailText": "Hvis du har anmodet om nulstilling af kodeordet til <%= username %> på Habitica, så gå til <%= passwordResetLink %> for at vælge et ny kodeord. Linket vil være gyldigt i 24 timer. Hvis du ikke har anmodet om nulstilling af kodeord, så venligst ignorer denne email.",
|
||||
"passwordResetEmailHtml": "Hvis du har anmodet om nulstilling af kodeordet til <strong><%= username %></strong> på Habitica, så <a href=\"<%= passwordResetLink %>\">klik her</a> for at vælge et nyt kodeord. Linket vil være gyldigt i 24 timer.<br/><br>Hvis du ikke har anmodet om nulstilling af kodeord, så venligst ignorer denne email.",
|
||||
"invalidLoginCredentialsLong": "Åh-åh - din e-mailadresse/login navn eller kodeord er forkert.\n- Sørg for at de er skrevet korrekt. Dit login navn og kodeord er versalfølsomt. \n- Måske har du tilmeldt dig via Facebook eller Google-login, i stedet for e-mail, så double-tjek ved at prøve med dem.\n- Hvis du har glemt dit kodekord klik på \"Glemt Kodeord\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "Der er ingen konto med disse legitimationsoplysninger.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your Profile Name.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Kontoen er blevet suspenderet",
|
||||
"unsupportedNetwork": "Dette netværk understøttes ikke i øjeblikket.",
|
||||
"cantDetachSocial": "Kontoen mangler en anden godkendelsesmetode; kan ikke udføre denne godkendelsesmetode.",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "Ikke nok Ædelsten!",
|
||||
"messageAuthPasswordMustMatch": ":password og :confirmPassword er ikke ens",
|
||||
"messageAuthCredentialsRequired": ":brugernavn, :e-mail, :kodeord, :valideretKodeord krævet",
|
||||
"messageAuthUsernameTaken": "Loginnavn er allerede taget",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "E-mailen er allerede i brug",
|
||||
"messageAuthNoUserFound": "Ingen bruger fundet.",
|
||||
"messageAuthMustBeLoggedIn": "Du skal være logget ind først.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Diverse",
|
||||
"showHeader": "Vis Sidehoved",
|
||||
"changePass": "Skift Kodeord",
|
||||
"changeUsername": "Skift Loginnavn",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Skift email-adresse",
|
||||
"newEmail": "Ny email-adresse",
|
||||
"oldPass": "Gammelt Kodeord",
|
||||
"newPass": "Nyt Kodeord",
|
||||
"confirmPass": "Bekræft Nyt Kodeord",
|
||||
"newUsername": "Nyt Loginnavn",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Farezone",
|
||||
"resetText1": "ADVARSEL! Dette nulstiller mange dele af din konto. Vi fraråder på det kraftigste dette, men nogen finder det brugbart i begyndelsen, efter at have spillet i kort tid.",
|
||||
"resetText2": "You will lose all your levels, Gold, and Experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "Den givne nulstillingskode til kodeord er ugyldig eller udløbet.",
|
||||
"passwordChangeSuccess": "Dit kodeord er nu ændret til den netop valgte. Du kan nu bruge det til at få adgang til din konto.",
|
||||
"passwordSuccess": "Dit Kodeord er skiftet",
|
||||
"usernameSuccess": "Dit Loginnavn er nu ændret",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Din Email er skiftet",
|
||||
"detachSocial": "Af-registrer <%= network %>",
|
||||
"detachedSocial": "Vi har fjernet godkendelse med <%= network %> fra din konto.",
|
||||
"addedLocalAuth": "Lokal godkendelse er nu aktiveret",
|
||||
"data": "Data",
|
||||
"exportData": "Eksportér Data",
|
||||
"usernameOrEmail": "Loginnavn eller Email",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Email",
|
||||
"registerWithSocial": "Registrer med <%= network %>",
|
||||
"registeredWithSocial": "Registreret med <%= network %>",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Vis Invitér til Gruppe-knap når gruppen har 1 medlem.",
|
||||
"saveCustomDayStart": "Gem Brugerdefineret Dagstart",
|
||||
"registration": "Registrering",
|
||||
"addLocalAuth": "Tilføj lokal godkendelse:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Generér Koder",
|
||||
"generate": "Generér",
|
||||
"getCodes": "Hent Koder",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Habitica bruger tidszonen sat på din PC, som er: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "Hvis tidszonen er forkert, kan du først genindlæse denne side ved hjælp af din browsers knap til genindlæsning for at give Habitica de mest opdaterede informationer. Hvis den stadig er forkert, kan du justere din tidszone på din PC og derefter genindlæse siden igen.<br><br> <strong>Hvis du bruger Habitica på andre PC'er eller mobile enheder, skal tidszonen være den samme på dem alle.</strong> Hvis dine Daglige er blevet nulstellet på det forkerte tidspunkt. kan du gentage denne gennemgang på alle andre PC'er og i en browser på dine mobile enheder.",
|
||||
"push": "Push",
|
||||
"about": "About"
|
||||
"about": "About",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Anderes",
|
||||
"fullName": "Name",
|
||||
"displayName": "Angezeigter Name",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Foto",
|
||||
"displayBlurb": "Über mich ...",
|
||||
"displayBlurbPlaceholder": "Stelle Dich bitte vor",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "Du hast etwas Mana verwendet",
|
||||
"lostHealth": "Du hast etwas Lebenspunkte verloren",
|
||||
"lostExperience": "Dir wurden Erfahrungspunkte abgezogen",
|
||||
"displayNameDescription1": "Dieser wird bei Nachrichten, die Du im Gasthaus postest, sowie im Gruppenchat und auch auf Deinem Avatar angezeigt. Um den Namen anzupassen, verwende den Bearbeiten-Knopf oben. Wenn Du stattdessen Deinen Anmeldenamen ändern willst, gehe zu",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Einstellungen->Seite",
|
||||
"displayNameDescription3": "und vergleiche auch den Abschnitt Registrierung.",
|
||||
"unequipBattleGear": "Kampfausrüstung ablegen",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Schalte neue, motivierende Werkzeuge frei, wie zum Beispiel Haustiere, zufällige Belohnungen, Zaubersprüche und mehr!",
|
||||
"unlockHeadline": "Je mehr Du tust, desto mehr neue Inhalte kannst Du freigeschalten!",
|
||||
"useUUID": "Benutze UUID / API Token (Für Facebook-Benutzer)",
|
||||
"username": "Anmeldename",
|
||||
"emailOrUsername": "E-Mail-Adresse oder Anmeldename (Groß- und Kleinschreibung beachten)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Sehen Sie sich die Videos an",
|
||||
"work": "Arbeit",
|
||||
"zelahQuote": "Mit [Habitica] kann ich mich davon überzeugen, rechtzeitig ins Bett zu gehen, denn wenn ich früh ins Bett gehe, verdiene ich Punkte und wenn ich zu lange aufbleibe, verliere ich Leben.",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Authentifizierungsheader fehlen.",
|
||||
"missingAuthParams": "Authentifizierungsparameter fehlen.",
|
||||
"missingUsernameEmail": "Fehlender Anmeldename oder E-Mail Adresse.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Fehlende E-Mail.",
|
||||
"missingUsername": "Fehlender Anmeldename.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Fehlendes Passwort.",
|
||||
"missingNewPassword": "Fehlendes neues Passwort.",
|
||||
"invalidEmailDomain": "Du kannst E-Mails mit den folgenden Domains nicht registrieren: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Diese E-Mail-Adresse wird bereits von einem Konto verwendet.",
|
||||
"newEmailRequired": "Fehlende neue E-Mail-Adresse.",
|
||||
"usernameTime": "It's time to set your username!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Passwort-Reset für Habitica",
|
||||
"passwordResetEmailText": "Wenn Du das Passwort für <%= username %> zurücksetzen möchtest, folge bitte dem Link <%= passwordResetLink %>, um ein neues zu setzen. Dieser Link wird in 24 Stunden ungültig. Wenn du kein Passwort-Reset angefordert hast, kannst Du diese E-Mail ignorieren.",
|
||||
"passwordResetEmailHtml": "Wenn Du das Passwort für <strong><%= username %></strong> auf Habitica zurücksetzen möchtest, folge bitte <a href=\"<%= passwordResetLink %>\">diesem Link </a>, um ein neues zu setzen. Dieser Link wird in 24 Stunden ungültig.<br/><br>Wenn du kein Passwort-Reset angefordert hast, kannst Du diese E-Mail ignorieren.",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / login name or password is incorrect.\n- Make sure they are typed correctly. Your login name and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "Es gibt kein Konto, das diese Anmeldedaten verwendet.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your Profile Name.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Dieser Account wurde suspendiert. ",
|
||||
"unsupportedNetwork": "Dieses Netzwerk wird aktuell nicht unterstützt.",
|
||||
"cantDetachSocial": "Der Account hat nur noch diese Authentifizierung, sie kann nicht getrennt werden.",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "Nicht genügend Edelsteine!",
|
||||
"messageAuthPasswordMustMatch": ":password und :confirmPassword stimmen nicht überein",
|
||||
"messageAuthCredentialsRequired": ":username, :email, :password, :confirmPassword erforderlich",
|
||||
"messageAuthUsernameTaken": "Anmeldename ist schon vergeben",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "E-Mail existiert bereits",
|
||||
"messageAuthNoUserFound": "Kein Benutzer gefunden.",
|
||||
"messageAuthMustBeLoggedIn": "Du musst angemeldet sein.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Verschiedenes",
|
||||
"showHeader": "Header anzeigen",
|
||||
"changePass": "Passwort ändern",
|
||||
"changeUsername": "Anmeldename ändern",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "E-Mail-Adresse ändern",
|
||||
"newEmail": "Neue E-Mail-Adresse",
|
||||
"oldPass": "Altes Passwort",
|
||||
"newPass": "Neues Passwort",
|
||||
"confirmPass": "Neues Passwort bestätigen",
|
||||
"newUsername": "Neuen Anmeldenamen erstellen",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Gefahrenzone",
|
||||
"resetText1": "WARNUNG! Es werden große Teile Deines Accounts zurückgesetzt. Wir raten dringend davon ab. Jedoch finden einige Spieler diese Funktion sinnvoll, um nach einem anfänglichen Testen der Seite neu beginnen zu können.",
|
||||
"resetText2": "Du verlierst alle Deine Level, Gold- und Erfahrungspunkte. Alle Deine Aufgaben (außer Wettbewerbsaufgaben) werden permanent gelöscht inklusive ihrer historischen Daten. Du verlierst Deine gesamte Ausrüstung, kannst sie aber zurück kaufen, inklusive der limitierten Ausgaben oder der mysteriösen Abonnenten-Gegenstände, die Du bereits besitzt (allerdings musst Du für klassenspezifische Ausrüstung der richtigen Klasse angehören, um sie zurückzukaufen). Du behältst Deine aktuelle Klasse und Deine Haus- und Reittiere. Möglicherweise möchtest Du lieber eine Sphäre der Wiedergeburt nutzen, die eine weit sicherere Option darstellt und Deine Aufgaben und Ausrüstung beibehält.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "Der übermittelte Passwort-Reset-Code ist ungültig oder abgelaufen.",
|
||||
"passwordChangeSuccess": "Dein Passwort wurde erfolgreich geändert. Du kannst das Passwort jetzt nutzen, um auf Deinen Account zuzugreifen.",
|
||||
"passwordSuccess": "Das Passwort wurde erfolgreich geändert",
|
||||
"usernameSuccess": "Dein Anmeldename wurde erfolgreich geändert!",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Die E-Mail-Adresse wurde erfolgreich geändert",
|
||||
"detachSocial": "<%= network %> trennen",
|
||||
"detachedSocial": "Erfolgreich die Verbindung zu <%= network %> getrennt",
|
||||
"addedLocalAuth": "Lokale Authentifizierung erfolgreich hinzugefügt",
|
||||
"data": "Daten",
|
||||
"exportData": "Daten exportieren",
|
||||
"usernameOrEmail": "Anmeldename oder E-Mail-Adresse",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "E-Mail",
|
||||
"registerWithSocial": "Mit <%= network %> verbinden",
|
||||
"registeredWithSocial": "Mit <%= network %> verbunden",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Zeige \"In Gruppe einladen\"-Schaltfläche an, wenn die Gruppe nur 1 Mitglied hat.",
|
||||
"saveCustomDayStart": "Speichere den Tageswechsel",
|
||||
"registration": "Registrierung",
|
||||
"addLocalAuth": "Lokale Authentifizierung hinzufügen:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Erstelle Codes",
|
||||
"generate": "Erstelle",
|
||||
"getCodes": "Codes erhalten!",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Habitica verwendet die Zeitzone, welche an Deinem PC eingestellt ist: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "Wenn diese Zeitzone falsch ist, lade die Seite mit Hilfe Deines Browsers erneut, um sicherzustellen, dass Habitica die aktuellen Informationen darstellt. Ist diese immernoch falsch, passe die Zeitzone Deines PCs an und lade die Seite erneut.<br><br> <strong>Wenn Du Habitica auf anderen PCs oder Mobilgeräten verwendest, muss die Zeitzone auf allen übereinstimmen.</strong> Wenn Deine täglichen Aufgaben zur falschen Zeit zurückgesetzt werden, wiederhole diese Prüfung auf allen anderen PCs und in einem Browser Deiner Mobilgeräte.",
|
||||
"push": "Push",
|
||||
"about": "Über"
|
||||
"about": "Über",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Other",
|
||||
"fullName": "Full Name",
|
||||
"displayName": "Display Name",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Photo",
|
||||
"displayBlurb": "Blurb",
|
||||
"displayBlurbPlaceholder": "Please introduce yourself",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "You used some Mana",
|
||||
"lostHealth": "You lost some Health",
|
||||
"lostExperience": "You lost some Experience",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your login name, go to",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Settings->Site",
|
||||
"displayNameDescription3": "and look in the Registration section.",
|
||||
"unequipBattleGear": "Unequip Battle Gear",
|
||||
@@ -204,6 +206,7 @@
|
||||
"hideQuickAllocation": "Hide Stat Allocation",
|
||||
"quickAllocationLevelPopover": "Each level earns you one Point to assign to a Stat of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User Icon > Stats.",
|
||||
"notEnoughAttrPoints": "You don't have enough Stat Points.",
|
||||
"classNotSelected": "You must select Class before you can assign Stat Points.",
|
||||
"style": "Style",
|
||||
"facialhair": "Facial",
|
||||
"photo": "Photo",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Unlock new motivational tools, such as pet collecting, random rewards, spell-casting, and more!",
|
||||
"unlockHeadline": "As you stay productive, you unlock new content!",
|
||||
"useUUID": "Use UUID / API Token (For Facebook Users)",
|
||||
"username": "Login Name",
|
||||
"emailOrUsername": "Email or Login Name (case-sensitive)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Watch Videos",
|
||||
"work": "Work",
|
||||
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Missing authentication headers.",
|
||||
"missingAuthParams": "Missing authentication parameters.",
|
||||
"missingUsernameEmail": "Missing Login Name or email.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Missing email.",
|
||||
"missingUsername": "Missing Login Name.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Missing password.",
|
||||
"missingNewPassword": "Missing new password.",
|
||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Email address is already used in an account.",
|
||||
"newEmailRequired": "Missing new email address.",
|
||||
"usernameTime": "It's time to set your username!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Password Reset for Habitica",
|
||||
"passwordResetEmailText": "If you requested a password reset for <%= username %> on Habitica, head to <%= passwordResetLink %> to set a new one. The link will expire after 24 hours. If you haven't requested a password reset, please ignore this email.",
|
||||
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / login name or password is incorrect.\n- Make sure they are typed correctly. Your login name and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "There is no account that uses those credentials.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your Profile Name.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Account has been suspended",
|
||||
"unsupportedNetwork": "This network is not currently supported.",
|
||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"innText": "You're resting in the Inn! While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day. Be warned: If you are participating in a Boss Quest, the Boss will still damage you for your Party mates' missed Dailies unless they are also in the Inn! Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn.",
|
||||
"innTextBroken": "You're resting in the Inn, I guess... While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day... If you are participating in a Boss Quest, the Boss will still damage you for your Party mates' missed Dailies... unless they are also in the Inn... Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn... so tired...",
|
||||
"innCheckOutBanner": "You are currently checked into the Inn. Your Dailies won't damage you and you won't make progress towards Quests.",
|
||||
"innCheckOutBannerShort": "You are checked into the Inn.",
|
||||
"resumeDamage": "Resume Damage",
|
||||
"helpfulLinks": "Helpful Links",
|
||||
"communityGuidelinesLink": "Community Guidelines",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
"messageAuthPasswordMustMatch": ":password and :confirmPassword don't match",
|
||||
"messageAuthCredentialsRequired": ":username, :email, :password, :confirmPassword required",
|
||||
"messageAuthUsernameTaken": "Login Name already taken",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "Email already taken",
|
||||
"messageAuthNoUserFound": "No user found.",
|
||||
"messageAuthMustBeLoggedIn": "You must be logged in.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Misc",
|
||||
"showHeader": "Show Header",
|
||||
"changePass": "Change Password",
|
||||
"changeUsername": "Change Login Name",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Change Email Address",
|
||||
"newEmail": "New Email Address",
|
||||
"oldPass": "Old Password",
|
||||
"newPass": "New Password",
|
||||
"confirmPass": "Confirm New Password",
|
||||
"newUsername": "New Login Name",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Danger Zone",
|
||||
"resetText1": "WARNING! This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
|
||||
"resetText2": "You will lose all your levels, Gold, and Experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "The supplied password reset code is invalid or has expired.",
|
||||
"passwordChangeSuccess": "Your password was successfully changed to the one you just chose. You can now use it to access your account.",
|
||||
"passwordSuccess": "Password successfully changed",
|
||||
"usernameSuccess": "Login Name successfully changed",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Email successfully changed",
|
||||
"detachSocial": "De-register <%= network %>",
|
||||
"detachedSocial": "Successfully removed <%= network %> authentication from your account",
|
||||
"addedLocalAuth": "Successfully added local authentication",
|
||||
"data": "Data",
|
||||
"exportData": "Export Data",
|
||||
"usernameOrEmail": "Login Name or Email",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Email",
|
||||
"registerWithSocial": "Register with <%= network %>",
|
||||
"registeredWithSocial": "Registered with <%= network %>",
|
||||
@@ -151,7 +152,7 @@
|
||||
"couponText": "We sometimes have events and give out coupon codes for special gear. (eg, those who stop by our Wondercon booth)",
|
||||
"saveCustomDayStart": "Save Custom Day Start",
|
||||
"registration": "Registration",
|
||||
"addLocalAuth": "Add local authentication:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Generate Codes",
|
||||
"generate": "Generate",
|
||||
"getCodes": "Get Codes",
|
||||
@@ -189,5 +190,18 @@
|
||||
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.<br><br> <strong>If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all.</strong> If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices.",
|
||||
"push": "Push",
|
||||
"about": "About"
|
||||
"about": "About",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Other",
|
||||
"fullName": "Full Name",
|
||||
"displayName": "Display Name",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Photo",
|
||||
"displayBlurb": "Blurb",
|
||||
"displayBlurbPlaceholder": "Please introduce yeself",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "You used some Mana",
|
||||
"lostHealth": "You lost some Health",
|
||||
"lostExperience": "You lost some Experience",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your login name, go to",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Settings->Site",
|
||||
"displayNameDescription3": "and look in the Registration section.",
|
||||
"unequipBattleGear": "Unequip Battle Gear",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Unlock new motivational tools, such as pet collecting, random rewards, spell-casting, an' more!",
|
||||
"unlockHeadline": "As ye stay productive, ye unlock new content!",
|
||||
"useUUID": "Use UUID / API Token (For Facebook Users)",
|
||||
"username": "Login Name",
|
||||
"emailOrUsername": "Email or Login Name (case-sensitive)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Watch Videos",
|
||||
"work": "Work",
|
||||
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Missing authentication headers.",
|
||||
"missingAuthParams": "Missing authentication parameters.",
|
||||
"missingUsernameEmail": "Missing Login Name or email.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Missing email.",
|
||||
"missingUsername": "Missing Login Name.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Missing password.",
|
||||
"missingNewPassword": "Missing new password.",
|
||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Email address is already used in an account.",
|
||||
"newEmailRequired": "Missing new email address.",
|
||||
"usernameTime": "It's time to set your username!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Password Reset for Habitica",
|
||||
"passwordResetEmailText": "If you requested a password reset for <%= username %> on Habitica, head to <%= passwordResetLink %> to set a new one. The link will expire after 24 hours. If you haven't requested a password reset, please ignore this email.",
|
||||
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / login name or password is incorrect.\n- Make sure they are typed correctly. Your login name and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "There is no account that uses those credentials.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been made to walk the plank for breaking the [Rules of th’ Sea](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be retrieved from the sea, please cast a message in a bottle to our Community Manager at <%= communityManagerEmail %> or ask yer parent or guardian to do so. Please copy your User ID into the email and include your Profile Name.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Account has been suspended",
|
||||
"unsupportedNetwork": "This network is not currently supported.",
|
||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "Not enough gems!",
|
||||
"messageAuthPasswordMustMatch": ":password and :confirmPassword don' match",
|
||||
"messageAuthCredentialsRequired": ":username, :email, :password, :confirmPassword required",
|
||||
"messageAuthUsernameTaken": "Login Name already taken",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "Email already taken",
|
||||
"messageAuthNoUserFound": "No user c'n be found.",
|
||||
"messageAuthMustBeLoggedIn": "Ye must be logged in.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Misc",
|
||||
"showHeader": "Show Header",
|
||||
"changePass": "Change Password",
|
||||
"changeUsername": "Change Login Name",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Change Email Address",
|
||||
"newEmail": "New Email Address",
|
||||
"oldPass": "Old Password",
|
||||
"newPass": "New Password",
|
||||
"confirmPass": "Confirm yer New Password",
|
||||
"newUsername": "New Login Name",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Danger Zone",
|
||||
"resetText1": "AHOY! 'tis resets many parts 'o ye account. 'tis be highly discouraged, but some people find it useful in th' beginnin' after playin' wit' th' site fer a short time.",
|
||||
"resetText2": "You will lose all your levels, Gold, and Experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "The supplied password reset code is invalid or has expired.",
|
||||
"passwordChangeSuccess": "Your password was successfully changed to the one you just chose. You can now use it to access your account.",
|
||||
"passwordSuccess": "Password successfully changed",
|
||||
"usernameSuccess": "Login Name successfully changed",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Email successfully changed",
|
||||
"detachSocial": "De-register <%= network %>",
|
||||
"detachedSocial": "Successfully removed <%= network %> authentication from your account",
|
||||
"addedLocalAuth": "Successfully added local authentication",
|
||||
"data": "Data",
|
||||
"exportData": "Export Data",
|
||||
"usernameOrEmail": "Login Name or Email",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Email",
|
||||
"registerWithSocial": "Register with <%= network %>",
|
||||
"registeredWithSocial": "Registered with <%= network %>",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Display Invite T' Crew button when crew has 1 member.",
|
||||
"saveCustomDayStart": "Save Custom Day Start",
|
||||
"registration": "Registration",
|
||||
"addLocalAuth": "Add local authentication:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Generate Codes",
|
||||
"generate": "Generate",
|
||||
"getCodes": "Get Codes",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Habitica uses the time zone set on yer PC, which is: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "If that time zone is wrong, first reload this page using yer browser's reload or refresh button t' ensure that Habitica has th' most recent information. If it is still wrong, adjust th' time zone on yer PC an' then reload this page again.<br><br> <strong>If ye use Habitica on other PCs or mobile devices, th' time zone must be the same on them all.</strong> If yer Dailies have been resetting at th' wrong time, repeat this check on all other PCs an' on a browser on your mobile devices.",
|
||||
"push": "Push",
|
||||
"about": "About"
|
||||
"about": "About",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Other",
|
||||
"fullName": "Full Name",
|
||||
"displayName": "Display Name",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Photo",
|
||||
"displayBlurb": "Blurb",
|
||||
"displayBlurbPlaceholder": "Please introduce yourself",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "You used some Mana",
|
||||
"lostHealth": "You lost some Health",
|
||||
"lostExperience": "You lost some Experience",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your login name, go to",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Settings->Site",
|
||||
"displayNameDescription3": "and look in the Registration section.",
|
||||
"unequipBattleGear": "Unequip Battle Gear",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Unlock new motivational tools such as pet collecting, random rewards, spell-casting, and more!",
|
||||
"unlockHeadline": "As you stay productive, you unlock new content!",
|
||||
"useUUID": "Use UUID / API Token (For Facebook Users)",
|
||||
"username": "Login Name",
|
||||
"emailOrUsername": "Email or Login Name (case-sensitive)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Watch Videos",
|
||||
"work": "Work",
|
||||
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Missing authentication headers.",
|
||||
"missingAuthParams": "Missing authentication parameters.",
|
||||
"missingUsernameEmail": "Missing Login Name or email.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Missing email.",
|
||||
"missingUsername": "Missing Login Name.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Missing password.",
|
||||
"missingNewPassword": "Missing new password.",
|
||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Email address is already used in an account.",
|
||||
"newEmailRequired": "Missing new email address.",
|
||||
"usernameTime": "It's time to set your username!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Password Reset for Habitica",
|
||||
"passwordResetEmailText": "If you requested a password reset for <%= username %> on Habitica, head to <%= passwordResetLink %> to set a new one. The link will expire after 24 hours. If you haven't requested a password reset, please ignore this email.",
|
||||
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / login name or password is incorrect.\n- Make sure they are typed correctly. Your login name and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "There is no account that uses those credentials.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your Profile Name.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Account has been suspended",
|
||||
"unsupportedNetwork": "This network is not currently supported.",
|
||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "Not enough gems!",
|
||||
"messageAuthPasswordMustMatch": ":password and :confirmPassword don't match",
|
||||
"messageAuthCredentialsRequired": ":username, :email, :password, :confirmPassword required",
|
||||
"messageAuthUsernameTaken": "Login Name already taken",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "Email already taken",
|
||||
"messageAuthNoUserFound": "No user found.",
|
||||
"messageAuthMustBeLoggedIn": "You must be logged in.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Misc",
|
||||
"showHeader": "Show Header",
|
||||
"changePass": "Change Password",
|
||||
"changeUsername": "Change Login Name",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Change Email Address",
|
||||
"newEmail": "New Email Address",
|
||||
"oldPass": "Old Password",
|
||||
"newPass": "New Password",
|
||||
"confirmPass": "Confirm New Password",
|
||||
"newUsername": "New Login Name",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Danger Zone",
|
||||
"resetText1": "WARNING! This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
|
||||
"resetText2": "You will lose all your levels, Gold, and Experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "The supplied password reset code is invalid or has expired.",
|
||||
"passwordChangeSuccess": "Your password was successfully changed to the one you just chose. You can now use it to access your account.",
|
||||
"passwordSuccess": "Password successfully changed",
|
||||
"usernameSuccess": "Login Name successfully changed",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Email successfully changed",
|
||||
"detachSocial": "De-register <%= network %>",
|
||||
"detachedSocial": "Successfully removed <%= network %> authentication from your account",
|
||||
"addedLocalAuth": "Successfully added local authentication",
|
||||
"data": "Data",
|
||||
"exportData": "Export Data",
|
||||
"usernameOrEmail": "Login Name or Email",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Email",
|
||||
"registerWithSocial": "Register with <%= network %>",
|
||||
"registeredWithSocial": "Registered with <%= network %>",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member.",
|
||||
"saveCustomDayStart": "Save Custom Day Start",
|
||||
"registration": "Registration",
|
||||
"addLocalAuth": "Add local authentication:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Generate Codes",
|
||||
"generate": "Generate",
|
||||
"getCodes": "Get Codes",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.<br><br> <strong>If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all.</strong> If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices.",
|
||||
"push": "Push",
|
||||
"about": "About"
|
||||
"about": "About",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Otro",
|
||||
"fullName": "Nombre completo",
|
||||
"displayName": "Nombre de usuario",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Foto",
|
||||
"displayBlurb": "Biografía",
|
||||
"displayBlurbPlaceholder": "Preséntate",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "Has utilizado algún Maná",
|
||||
"lostHealth": "Has perdido algo de Salud",
|
||||
"lostExperience": "Has perdido algo de\nExperiencia",
|
||||
"displayNameDescription1": "Esto es lo que aparece en los mensajes que publicas en la Taberna, los gremios y el chat de grupo, junto con lo que se muestra en tu personaje. Para cambiarlo, haz clic en el botón superior de Editar. En cambio si quieres cambiar tu nombre de inicio de sesión, ve a",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Ajustes->Sitio",
|
||||
"displayNameDescription3": "y mira en la sección de Registro.",
|
||||
"unequipBattleGear": "Quitarse el equipo de batalla",
|
||||
@@ -204,7 +206,7 @@
|
||||
"hideQuickAllocation": "Esconder la asignación de puntos",
|
||||
"quickAllocationLevelPopover": "Cada nivel te da un Punto para asignar a la Estadística que quieras. Puedes hacerlo manualmente o dejar que el juego decida por ti, utilizando una de las opciones de Asignación Automática que encontrarás en Icono de Usuario > Estadísticas.",
|
||||
"notEnoughAttrPoints": "No tienes suficientes puntos de atributo.",
|
||||
"classNotSelected": "You must select Class before you can assign Stat Points.",
|
||||
"classNotSelected": "Debes seleccionar tu Clase antes de asignar los Puntos de Estadística.",
|
||||
"style": "Estilo",
|
||||
"facialhair": "Facial",
|
||||
"photo": "Foto",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"lostAllHealth": "¡Te quedaste sin Salud!",
|
||||
"dontDespair": "¡No desesperes!",
|
||||
"deathPenaltyDetails": "Has perdido un Nivel, tu oro y una pieza de equipamiento, pero puedes recuperarlos todos con trabajo duro! Buena suerte--lo harás genial.",
|
||||
"refillHealthTryAgain": "Rellena Salud & Vuelve a Intentarlo",
|
||||
"dontDespair": "¡No te desesperes!",
|
||||
"deathPenaltyDetails": "Has perdido un Nivel, tu Oro y una pieza de equipamiento, ¡pero puedes recuperarlos todos con trabajo duro! Buena suerte--lo harás genial.",
|
||||
"refillHealthTryAgain": "Rellena la salud y vuelve a intentarlo",
|
||||
"dyingOftenTips": "¿Pasa esto a menudo? <a href='http://habitica.wikia.com/wiki/Death_Mechanics#Strategies_for_Staying_Alive' target='_blank'>¡Aqui tienes ayuda!</a>",
|
||||
"losingHealthWarning": "¡Cuidado, estás perdiendo salud!",
|
||||
"losingHealthWarning2": "¡No dejes que tu salud baje a cero! Si lo haces, perderas un nivel, tu oro y una pieza de equipamiento.",
|
||||
"losingHealthWarning2": "¡No dejes que tu salud baje a cero! Si lo haces, perderás un nivel, tu oro y una pieza de equipamiento.",
|
||||
"toRegainHealth": "Para recuperar salud:",
|
||||
"lowHealthTips1": "Sube de nivel y te curarás del todo.",
|
||||
"lowHealthTips2": "Compra una Poción de Salud en la columna Recompensas para recuperar 15 puntos de salud.",
|
||||
@@ -13,5 +13,5 @@
|
||||
"lowHealthTips3": "Las tareas diarias que no cumples te dañan por la noche: no añadas demasiadas al principio.",
|
||||
"lowHealthTips4": "Si tienes alguna tarea diaria que no sea necesario realizar un día concreto, haz clic en el icono del lápiz y desactiva ese día.",
|
||||
"goodLuck": "¡Buena suerte!",
|
||||
"cannotRevive": "No se puede revivir si el jugador no está muerto."
|
||||
"cannotRevive": "No puedes revivir si no has muerto"
|
||||
}
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "¡Desbloquea nuevas herramientas motivacionales, como coleccionar mastocas, recompensas aleatorias, lanzamiento de hechizos y más!",
|
||||
"unlockHeadline": "¡Cuando eres productivo, desbloqueas nuevo contenido!",
|
||||
"useUUID": "Utilizar UUID / API Token (Para Usuarios de Facebook)",
|
||||
"username": "Nombre de usuario",
|
||||
"emailOrUsername": "Email o Nombre de Usuario (reconoce las mayúsculas)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Ver Vídeos",
|
||||
"work": "Trabajar",
|
||||
"zelahQuote": "Con [Habitica], Puedo irme a la cama a tiempo pensando en ganar puntos por acostarme pronto o perder salud por hacerlo tarde.",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Faltan los encabezados de autentificación. ",
|
||||
"missingAuthParams": "Faltan los parámetros de autentificación. ",
|
||||
"missingUsernameEmail": "He olvidado el Nombre de Usuario o el email",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Falta el correo electrónico.",
|
||||
"missingUsername": "Nombre de Usuario olvidado.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Falta la contraseña.",
|
||||
"missingNewPassword": "Falta una nueva contraseña.",
|
||||
"invalidEmailDomain": "No puedes registrar con emails con los siguientes dominios: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Ya existe una cuenta con esa dirección de correo electrónico.",
|
||||
"newEmailRequired": "Falta la nueva dirección de correo electrónico.",
|
||||
"usernameTime": "¡Es la hora de establecer tu nombre de usuario!",
|
||||
"usernameInfo": "Tu nombre mostrado no ha cambiado, pero tu antiguo nombre de inicio de sesión va a convertirse en tu nombre de usuario. Ese nombre de usuario se utilizará para invitaciones, @menciones en los chats y los mensajes. <br><br>Si quieres saber más sobre este cambio, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visita nuestra wiki</a>.",
|
||||
"usernameInfo": "Tu nombre público no ha cambiado, pero tu antiguo nombre de acceso se convertirá en tu nombre de usuario. Este nombre de usuario se utilizará para invitaciones, @menicones en los chats y mensajes. <br><br>Si quieres saber más sobre este cambio, visita la página de la wiki <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Nombres de los Jugadores</a>.",
|
||||
"usernameTOSRequirements": "Los nombre de usuario deben adecuarse a nuestros Términos de Servicio y Normas de la Comunidad. Si no has establecido un nombre de inicio de sesión, tu nombre de usuario será autogenerado.",
|
||||
"usernameTaken": "Este nombre de usuario ya está cogido.",
|
||||
"usernameWrongLength": "El nombre de usuario debe tener una longitud de entre 1 y 20 caracteres.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Restablecer contraseña para Habitica.",
|
||||
"passwordResetEmailText": "Si has solicitado restablecer la contraseña del usuario <%= username %> en Habitica, entra en <%= passwordResetLink %> para establecer una nueva. El enlace expira tras 24 horas. Si no has solicitado restablecer una contraseña, por favor ignora este mensaje.",
|
||||
"passwordResetEmailHtml": "Si has solicitado restablecer la contraseña del usuario <strong><%= username %></strong> en Habitica, <a href=\"<%= passwordResetLink %>\">haz clic aquí</a> para establecer una nueva. El enlace expira tras 24 horas.<br/><br>Si no has solicitado restablecer una contraseña, por favor ignora este mensaje.",
|
||||
"invalidLoginCredentialsLong": "Oh-oh - tu dirección de correo electrónico o contraseña son incorrectos.\n- Asegúrate de que están escritos correctamente. Tu nombre de inicio de sesión y contraseña distinguen entre mayúsculas y minúsculas.\n- Puede que te hayas registrado con tu cuenta de Google o Facebook en lugar de tu correo electrónico, intenta iniciar sesión con alguna de ellas.\n- Si has olvidado tu contraseña, pulsa sobre \"¿Has olvidado la contraseña?\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "No hay ninguna cuenta con esas credenciales.",
|
||||
"accountSuspended": "Esta cuenta, de ID de usuario \"<%= userId %>\", ha sido bloqueada por incumplir las [Normas de la Comunidad] (https://habitica.com/static/community-guidelines) o los [Términos de Servicio] (https://habitica.com/static/terms). Para más detalles o solicitar ser desbloqueado, por favor, envía un correo electrónico a nuestro administrador de la comunidad en <%= communityManagerEmail %> o pregunta a tu padre o tutor que les envíen un correo. Por varor, copia tu ID de usuario en el correo e incluye tu nombre de usuario.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Esta cuenta ha sido suspendida",
|
||||
"unsupportedNetwork": "La red no está en servicio.",
|
||||
"cantDetachSocial": "La cuenta carece de otro método de autenticación; no se puede separar de este método de autenticación.",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"innText": "¡Estás descansando en la Posada! Mientras estés en ella, tus Tareas diarias no te causarán daño al final del día, pero seguirán restableciéndose cada día. Ten cuidado: si estás participando en una Misión contra un Jefe, el Jefe seguirá haciéndote daño cuando tus compañeros de equipo no cumplan sus Tareas diarias, ¡a no ser que ellos también estén en la Posada! Además, el daño que provoques al Jefe (o los objetos que encuentres) no se contará hasta que salgas de la Posada.",
|
||||
"innTextBroken": "Estás descansando en la Posada, o eso creo... Mientras estés en ella, tus Tareas diarias no te causarán daño al final del día, pero seguirán restableciéndose cada día... Si estás participando en una Misión contra un Jefe, el Jefe seguirá haciéndote daño cuando tus compañeros de equipo no cumplan sus Tareas diarias... a no ser que ellos también estén en la Posada... Además, el daño que provoques al Jefe (o los objetos que encuentres) no se contará hasta que salgas de la Posada... Qué cansancio...",
|
||||
"innCheckOutBanner": "Actualmente estás registrado en la Posada. Tus Tareas Diarias no te harán daño y no progresarás en las Misiones.",
|
||||
"innCheckOutBannerShort": "You are checked into the Inn.",
|
||||
"innCheckOutBannerShort": "Has entrado en la Taberna.",
|
||||
"resumeDamage": "Reanudar daño",
|
||||
"helpfulLinks": "Enlaces de interés",
|
||||
"communityGuidelinesLink": "Directrices de la Comunidad",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "No hay suficientes gemas!",
|
||||
"messageAuthPasswordMustMatch": "Las contraseñas no coinciden.",
|
||||
"messageAuthCredentialsRequired": ":username, :email, :password y :confirmPassword son obligatorios",
|
||||
"messageAuthUsernameTaken": "Nombre de usuario ya ocupado",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "El correo electrónico ya está en uso",
|
||||
"messageAuthNoUserFound": "No se encontró el usuario.",
|
||||
"messageAuthMustBeLoggedIn": "Debes estar identificado.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Varios",
|
||||
"showHeader": "Mostrar encabezamiento",
|
||||
"changePass": "Cambiar contraseña",
|
||||
"changeUsername": "Cambiar Nombre de Usuario",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Cambiar Correo Electrónico",
|
||||
"newEmail": "Nuevo Correo Electrónico",
|
||||
"oldPass": "Contraseña antigua",
|
||||
"newPass": "Contraseña nueva",
|
||||
"confirmPass": "Confirmar la nueva contraseña",
|
||||
"newUsername": "Nuevo Nombre de Usuario",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Zona Peligrosa",
|
||||
"resetText1": "¡PELIGRO! Eso resetea muchas partes de tu cuenta. Esto es altamente desalentador, pero algunas personas lo encuentran útil al principio después de jugar durante un tiempo.",
|
||||
"resetText2": "Perderás todos tus niveles, Oro y puntos de Experiencia. Todas tus tareas (excepto las de los desafíos) serán eliminadas permanentemente y tú perderás todo su historia. Perderás todo tu equipamiento, pero podrás comprarlo de nuevo, incluyendo todo el equipamiento de edición limitada o los objetos Misterio de suscriptor que ya posees (necesitarás estar en la clase correcta para comprar de nuevo Equipo específico de esa clase). Mantendrás tu actual clase y tus mascotas y monturas. Quizá prefieras utilizar un Orbe de Renacimiento en su lugar, que es una opción mucho más segura que mantendrá tus tareas y equipamiento.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "El código proporcionado para restablecer la contraseña no es válido o ha expirado.",
|
||||
"passwordChangeSuccess": "Tu contraseña se ha cambiado con éxito a la que has elegido. Ya puedes usarla para acceder a tu cuenta.",
|
||||
"passwordSuccess": "Contraseña cambiada con éxito",
|
||||
"usernameSuccess": "Nombre de Usuario cambiado con éxito",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Correo electrónico cambiado con éxito",
|
||||
"detachSocial": "Cancelar registro <%= network %>",
|
||||
"detachedSocial": "Se eliminó correctamente la <%= network %> identificación de su cuenta.",
|
||||
"addedLocalAuth": "La autenticación local se ha añadido correctamente",
|
||||
"data": "Datos",
|
||||
"exportData": "Exportar Datos",
|
||||
"usernameOrEmail": "Introduce tu Nombre o Correo electrónico.",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Correo Electrónico",
|
||||
"registerWithSocial": "Registrarte con <%= network %>",
|
||||
"registeredWithSocial": "Registrado con <%= network %>",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Mostrar el botón \"Invitar al equipo\" cuando el equipo tiene 1 miembro.",
|
||||
"saveCustomDayStart": "Guardar inicio de día personalizado",
|
||||
"registration": "Registro",
|
||||
"addLocalAuth": "Agregar autenticación local:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Generar códigos",
|
||||
"generate": "Generar",
|
||||
"getCodes": "Obtener Códigos",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Habitica usa la zona horaria de tu PC, que es: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "Si esa zona horaria es incorrecta, antes que nada, vuelve a cargar esta página con el botón de actualizar del navegador para asegurarte de que Habitica disponga de la información más reciente. Si sigue siendo errónea, configura la zona horaria en tu PC y, luego, vuelve a cargar esta página una vez más.<br><br> <strong>Si usas Habitica en otros ordenadores o dispositivos móviles, la zona horaria debe ser la misma en todos ellos.</strong> Si tus tareas diarias se restablecen a una hora incorrecta, vuelve a comprobar esto en todos los demás ordenadores y en un navegador de tus dispositivos móviles.",
|
||||
"push": "Push",
|
||||
"about": "Sobre Habitica"
|
||||
"about": "Sobre Habitica",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Otro",
|
||||
"fullName": "Nombre completo",
|
||||
"displayName": "Nombre para mostrar",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Foto",
|
||||
"displayBlurb": "Sobre mí",
|
||||
"displayBlurbPlaceholder": "Por favor, preséntate",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "Usaste algo de Maná",
|
||||
"lostHealth": "Perdiste algo de Salud",
|
||||
"lostExperience": "Perdiste algo de Experiencia",
|
||||
"displayNameDescription1": "Ésto es lo que aparece en los mensajes que publicas en la Taverna, gremios y chats de grupos, junto a lo que es mostrado en tu avatar. Ve a",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Ajustes -> Sitio",
|
||||
"displayNameDescription3": "y desplázate hacia la sección de Registro.",
|
||||
"unequipBattleGear": "Quitar Equipamient de batalla",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "¡Desbloquea nuevas herramientas motivacionales, tales como coleccionar mascotas, premios al azar, conjurar hechizos y más!",
|
||||
"unlockHeadline": "¡Al mantenerte productivo desbloqueas nuevo contenido!",
|
||||
"useUUID": "Utilizar UUID / Ficha API (Para Usuarios de Facebook)",
|
||||
"username": "Nombre de Usuario",
|
||||
"emailOrUsername": "Correo Electrónico o Nombre de Usuario (sensible a mayúsculas y minúsculas)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Ver videos",
|
||||
"work": "Trabajo",
|
||||
"zelahQuote": "¡Con [Habitica] puedo ser persuadido de ir a la cama a tiempo con la idea de ganar puntos por acostarme temprano o perder salud al acostarme tarde!",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "Faltan encabezados de autenticación.",
|
||||
"missingAuthParams": "Faltan parámetros de autenticación.",
|
||||
"missingUsernameEmail": "Falta Nombre de Usuario o Correo Electrónico.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Falta el correo electrónico.",
|
||||
"missingUsername": "Falta Nombre de Usuario.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Falta la contraseña.",
|
||||
"missingNewPassword": "Falta la contraseña nueva.",
|
||||
"invalidEmailDomain": "No puedes registrarte con correos electrónicos con los siguientes dominios: <%= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Esta dirección de correo electrónico ya está en uso.",
|
||||
"newEmailRequired": "El nuevo correo electronico no puede ser encontrado.",
|
||||
"usernameTime": "It's time to set your username!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Reinicio de contraseña para Habitica",
|
||||
"passwordResetEmailText": "If you requested a password reset for <%= username %> on Habitica, head to <%= passwordResetLink %> to set a new one. The link will expire after 24 hours. If you haven't requested a password reset, please ignore this email.",
|
||||
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - tu nombre de usuario o contraseña son incorrectos. \n- Asegúrate de que ambos estén escritos correctamente. Tu nombre de usuario y contraseña son sensibles a las mayúsculas y minúsculas\n- Puede ser que te hayas registrado con Facebook o Google,no con tu correo electrónico, así que verifícalo intentando iniciar sesión con ambos.\n- Si olvidaste tu contraseña, haz click en \"Olvidé mi contraseña\".",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "No hay ninguna cuenta que use esas credenciales.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your Profile Name.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Account has been suspended",
|
||||
"unsupportedNetwork": "Esta red no es suportada por el momento.",
|
||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"messageInsufficientGems": "¡No tienes suficientes gemas!",
|
||||
"messageAuthPasswordMustMatch": ":password y :confirmPassword no coinciden",
|
||||
"messageAuthCredentialsRequired": "Se necesitan :username, :email, :password, y :confirmPassword",
|
||||
"messageAuthUsernameTaken": "Nombre de Usuario ya existente.",
|
||||
"messageAuthUsernameTaken": "Username already taken",
|
||||
"messageAuthEmailTaken": "Esta dirección de correo electrónico no está disponible",
|
||||
"messageAuthNoUserFound": "Usuario no encontrado.",
|
||||
"messageAuthMustBeLoggedIn": "Tienes que iniciar sesíon.",
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
"misc": "Varios",
|
||||
"showHeader": "Mostrar cabecera",
|
||||
"changePass": "Cambiar contraseña",
|
||||
"changeUsername": "Cambiar nombre de usuario",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Cambiar dirección de correo electrónico",
|
||||
"newEmail": "Nueva dirección de correo electrónico",
|
||||
"oldPass": "Contraseña anterior",
|
||||
"newPass": "Contraseña nueva",
|
||||
"confirmPass": "Confirmar nueva contraseña",
|
||||
"newUsername": "Nuevo nombre de usuario",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Zona peligrosa",
|
||||
"resetText1": "¡AVISO! Esto reinicia muchas partes de tu cuenta. Esto no es recomendable, pero ha resultado útil para algunas personas al principio después de jugar con el sitio web por un corto tiempo.",
|
||||
"resetText2": "Perderás todos tus niveles, Oro, y puntos de experiencia. Todas tus tareas (Excepto aquellas de Desafío) y tu historial serán borrados permanentemente. Perderás todo tu equipamiento pero podrás comprarlo de nuevo en el futuro, incluyendo todo el equipamiento de edición limitada o Artículos Misteriosos de suscriptor que ya posees (tendrás que estar en la clase correcta para poder volver a comprar equipamiento limitado a cierta clases). Te quedarás con tu clase actual, tus mascotas y monturas. Quiza prefieras usar un Orbe de Renacimiento en su lugar, el cual es una opción mucho mas seguta, la cual preservará tus tareas.",
|
||||
@@ -95,14 +95,15 @@
|
||||
"invalidPasswordResetCode": "El código para restablecer la contraseña que fue entregado es invalido o ha expirado.",
|
||||
"passwordChangeSuccess": "Tu contraseña ha sido cambiada exitosamente a la que acabas de elegir. Ahora puedes utilizarla para acceder a tu cuenta.",
|
||||
"passwordSuccess": "La contraseña fue cambiada exitosamente",
|
||||
"usernameSuccess": "El nombre de usuario fue cambiado exitosamente",
|
||||
"usernameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Tu correo electrónico fue cambiado exitosamente",
|
||||
"detachSocial": "Darse de baja <%= network %>",
|
||||
"detachedSocial": "La autenticación de <%= network %> ha sido removida con éxito de tu cuenta",
|
||||
"addedLocalAuth": "Autenticación local añadida exitosamente",
|
||||
"data": "Datos",
|
||||
"exportData": "Exportar datos",
|
||||
"usernameOrEmail": "Nombre de usuario o correo electrónico",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"email": "Correo electrónico",
|
||||
"registerWithSocial": "Registrarse con <%= network %>",
|
||||
"registeredWithSocial": "Registrado con <%= network %>",
|
||||
@@ -150,7 +151,7 @@
|
||||
"displayInviteToPartyWhenPartyIs1": "Mostrar el botón \"Invitar a Equipo\" cuando el equipo tiene 1 miembro.",
|
||||
"saveCustomDayStart": "Guardar comienzo de día personalizado",
|
||||
"registration": "Registro",
|
||||
"addLocalAuth": "Agregar autenticación local:",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
"generateCodes": "Generar códigos",
|
||||
"generate": "Generar",
|
||||
"getCodes": "Obtener códigos",
|
||||
@@ -188,5 +189,18 @@
|
||||
"timezoneUTC": "Habitica utiliza la zona horaria de tu PC, la cual es <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "Si esa zona horaria es incorrecta, primero vuelve a cargar esta página usando el botón de recarga o actualización de tu navegador para asegurarte de que Habitica tenga la información más reciente. Si todavía está mal, ajusta la zona horaria en su PC y luego vuelva a cargar esta página.<br><br><strong> Si usa Habitica en otras PC o dispositivos móviles, la zona horaria debe ser la misma para todos. Si tus Diarias se han reiniciado en el momento equivocado, repite este control en todas las otras PC y en un navegador en tus dispositivos móviles.",
|
||||
"push": "Empuja",
|
||||
"about": "Acerca de"
|
||||
"about": "Acerca de",
|
||||
"setUsernameNotificationTitle": "Confirm your username!",
|
||||
"setUsernameNotificationBody": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"usernameIssueSlur": "Usernames may not contain inapporpriate language.",
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters, numbers and underscores.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"displaynameIssueSlur": "Display Names may not contain inappropriate language",
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging."
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
"other": "Autres",
|
||||
"fullName": "Nom complet",
|
||||
"displayName": "Pseudonyme",
|
||||
"changeDisplayName": "Change Display Name",
|
||||
"newDisplayName": "New Display Name",
|
||||
"displayPhoto": "Photo",
|
||||
"displayBlurb": "Résumé",
|
||||
"displayBlurbPlaceholder": "Veuillez vous présenter",
|
||||
@@ -181,7 +183,7 @@
|
||||
"lostMana": "Vous avez utilisé du mana",
|
||||
"lostHealth": "Vous avez perdu de la vie",
|
||||
"lostExperience": "Vous avez perdu de l'expérience",
|
||||
"displayNameDescription1": "Voici ce qui apparaît dans les messages que vous publiez dans les discussions de la taverne, de guilde et d'équipe, ainsi que sur votre avatar. Pour le changer, cliquez sur le bouton Éditer ci-dessus. Si au lieu de cela vous voulez changer votre identifiant, allez sur",
|
||||
"displayNameDescription1": "This is what appears in messages you post in the Tavern, guilds, and party chat, along with what is displayed on your avatar. To change it, click the Edit button above. If instead you want to change your username, go to",
|
||||
"displayNameDescription2": "Paramètres -> Site",
|
||||
"displayNameDescription3": "et regardez dans la section Inscription.",
|
||||
"unequipBattleGear": "Ôter la tenue de combat",
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
"unlockByline2": "Déverrouillez de nouveaux moyens de motivation comme collectionner des familiers, lancer des sorts, obtenir des récompenses aléatoires et bien d'autres choses !",
|
||||
"unlockHeadline": "En restant productif, vous déverrouillez plus de contenu !",
|
||||
"useUUID": "Utilisez l'UUID / le jeton d'API (pour les utilisateurs Facebook)",
|
||||
"username": "Nom de connexion",
|
||||
"emailOrUsername": "Adresse courriel ou nom de connexion (sensible à la casse)",
|
||||
"username": "Username",
|
||||
"emailOrUsername": "Email or Username (case-sensitive)",
|
||||
"watchVideos": "Regarder les vidéos",
|
||||
"work": "Travailler",
|
||||
"zelahQuote": "Avec [Habitica], je me suis persuadé d'aller au lit à l'heure avec la simple idée de gagner des points en me couchant tôt ou de perdre de la santé en me couchant tard !",
|
||||
@@ -259,9 +259,9 @@
|
||||
"altAttrSlack": "Slack",
|
||||
"missingAuthHeaders": "En-têtes d'authentification manquants.",
|
||||
"missingAuthParams": "Paramètres d'authentification manquants.",
|
||||
"missingUsernameEmail": "Il manque le nom d'utilisateur ou l'adresse courriel.",
|
||||
"missingUsernameEmail": "Missing username or email.",
|
||||
"missingEmail": "Courriel manquant.",
|
||||
"missingUsername": "Il manque le nom d'utilisateur.",
|
||||
"missingUsername": "Missing username.",
|
||||
"missingPassword": "Mot de passe manquant.",
|
||||
"missingNewPassword": "Nouveau mot de passe manquant.",
|
||||
"invalidEmailDomain": "Vous ne pouvez pas vous enregistrer avec une adresse courriel appartenant aux domaines suivants : %= domains %>",
|
||||
@@ -271,7 +271,7 @@
|
||||
"emailTaken": "Adresse courriel déjà utilisée par un utilisateur.",
|
||||
"newEmailRequired": "Nouvelle adresse courriel manquante.",
|
||||
"usernameTime": "It's time to set your username!",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, <a href='http://habitica.wikia.com/wiki/Habitica_Wiki' target='_blank'>visit our wiki</a>.",
|
||||
"usernameInfo": "Your display name hasn't changed, but your old login name will now become your public username. This username will be used for invitations, @mentions in chat, and messaging.<br><br>If you'd like to learn more about this change, visit the wiki's <a href='http://habitica.wikia.com/wiki/Player_Names' target='_blank'>Player Names</a> page.",
|
||||
"usernameTOSRequirements": "Usernames must conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated.",
|
||||
"usernameTaken": "Username already taken.",
|
||||
"usernameWrongLength": "Username must be between 1 and 20 characters long.",
|
||||
@@ -287,9 +287,9 @@
|
||||
"passwordResetEmailSubject": "Mot de passe réinitialisé pour Habitica",
|
||||
"passwordResetEmailText": "Si vous avez demandé une réinitialisation de mot de passe pour <%= username %> sur Habitica, rendez-vous sur <%= passwordResetLink %> pour en définir un nouveau. Le lien expirera après 24 heures. Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer ce courriel.",
|
||||
"passwordResetEmailHtml": "Si vous avez demandé une réinitialisation de mot de passe pour <strong><%= username %></strong> sur Habitica, rendez-vous sur <a href=\"<%= passwordResetLink %>\"> ce lien</a> pour en définir un nouveau. Le lien expirera après 24 heures.<br/><br>Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer ce courriel.",
|
||||
"invalidLoginCredentialsLong": "Oups ! Votre adresse courriel / nom d'utilisateur ou votre mot de passe est incorrect.\n- Assurez-vous que vous avez tout bien orthographié. Le nom d'utilisateur et le mot de passe sont sensibles à la casse.\n- Vous vous êtes peut-être enregistré avec Facebook ou \"Google-sign-in\", et non avec votre adresse courriel. Vérifiez en essayant de vous connecter avec ces services.\n- Si vous avez oublié votre mot de passe, cliquez sur « Mot de passe oublié ».",
|
||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||
"invalidCredentials": "Aucun compte n'utilise cet identifiant.",
|
||||
"accountSuspended": "Ce compte, ID d'Utilisateur “<%= userId %>”, a été bloqué pour avoir enfreint les [règles de vie en communauté] (https://habitica.com/static/community-guidelines) ou les [conditions d’utilisation] (https://habitica.com/static/terms). Pour plus d’informations, ou pour demander un déblocage, merci d’envoyer un courriel à notre gestionnaire de la communauté : <%= communityManagerEmail %>, ou demandez à un parent ou à un tuteur de leur envoyer un courriel. Merci de copier votre ID d'Utilisateur dans le courriel et d'y inclure également votre Nom d’Utilisateur.",
|
||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
||||
"accountSuspendedTitle": "Le compte a été suspendu",
|
||||
"unsupportedNetwork": "Ce réseau n'est actuellement pas pris en charge.",
|
||||
"cantDetachSocial": "Votre compte n'a pas d'autres méthodes d'authentification; vous ne pouvez pas détacher cette méthode d'authentification.",
|
||||
|
||||
@@ -266,13 +266,13 @@
|
||||
"weaponSpecialSummer2018MageNotes": "Sous l’eau, la magie du feu, de la glace ou de l’électricité peut être dangereuse pour le mage qui la détient. Soigner le poison des vives, par contre, est très utile ! Augmente l'Intelligence de <%= int %> et la Perception de <%= per %>. Équipement en édition limitée de l’été 2018.",
|
||||
"weaponSpecialSummer2018HealerText": "Trident de Sirène Monarque",
|
||||
"weaponSpecialSummer2018HealerNotes": "D'un geste bienveillant, vous commandez aux eaux revigorantes de couler en vagues à travers votre équipement. Augmente l'intelligence de <%= int %>. Équipement en édition limitée de l'été 2018.",
|
||||
"weaponSpecialFall2018RogueText": "Vial of Clarity",
|
||||
"weaponSpecialFall2018RogueText": "Fiole de clarté",
|
||||
"weaponSpecialFall2018RogueNotes": "When you need to come back to your senses, when you need a little boost to make the right decision, take a deep breath and a sip. It'll be OK! Increases Strength by <%= str %>. Limited Edition 2018 Autumn Gear.",
|
||||
"weaponSpecialFall2018WarriorText": "Whip of Minos",
|
||||
"weaponSpecialFall2018WarriorText": "Fouet de Minos",
|
||||
"weaponSpecialFall2018WarriorNotes": "Not quite long enough to unwind behind you for keeping your bearings in a maze. Well, maybe a very small maze. Increases Strength by <%= str %>. Limited Edition 2018 Autumn Gear.",
|
||||
"weaponSpecialFall2018MageText": "Staff of Sweetness",
|
||||
"weaponSpecialFall2018MageNotes": "This is no ordinary lollipop! The glowing orb of magic sugar atop this staff has the power to make good habits stick to you. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2018 Autumn Gear. Two-handed item.",
|
||||
"weaponSpecialFall2018HealerText": "Starving Staff",
|
||||
"weaponSpecialFall2018HealerText": "Bâton glouton",
|
||||
"weaponSpecialFall2018HealerNotes": "Just keep this staff fed, and it will bestow Blessings. If you forget to feed it, keep your fingers out of reach. Increases Intelligence by <%= int %>. Limited Edition 2018 Autumn Gear.",
|
||||
"weaponMystery201411Text": "Fourche festive",
|
||||
"weaponMystery201411Notes": "Embrochez vos ennemis ou plantez-la dans votre nourriture préférée : cette fourche multi-fonctions peut tout faire ! N'apporte aucun bonus. Équipement d'abonné·e de novembre 2014.",
|
||||
@@ -606,9 +606,9 @@
|
||||
"armorSpecialFall2018RogueNotes": "Style for the day. Comfort and protection for the night. Increases Perception by <%= per %>. Limited Edition 2018 Autumn Gear.",
|
||||
"armorSpecialFall2018WarriorText": "Minotaur Platemail",
|
||||
"armorSpecialFall2018WarriorNotes": "Complete with hooves to drum a soothing cadence as you walk your meditative labyrinth. Increases Constitution by <%= con %>. Limited Edition 2018 Autumn Gear.",
|
||||
"armorSpecialFall2018MageText": "Candymancer's Robes",
|
||||
"armorSpecialFall2018MageText": "Habits de sucromancien",
|
||||
"armorSpecialFall2018MageNotes": "The fabric of these robes has magic candy woven right in! However, we recommend you not attempt to eat them. Increases Intelligence by <%= int %>. Limited Edition 2018 Autumn Gear.",
|
||||
"armorSpecialFall2018HealerText": "Robes of Carnivory",
|
||||
"armorSpecialFall2018HealerText": "Habits de carnivorie",
|
||||
"armorSpecialFall2018HealerNotes": "It's made from plants, but that doesn't mean it's vegetarian. Bad habits are afraid to come within miles of these robes. Increases Constitution by <%= con %>. Limited Edition 2018 Autumn Gear.",
|
||||
"armorMystery201402Text": "Robe du messager",
|
||||
"armorMystery201402Notes": "Chatoyante et solide, cette robe possède de nombreuses poches dans lesquelles transporter des lettres. N'apporte aucun bonus. Équipement d'abonné·e de février 2014.",
|
||||
@@ -680,7 +680,7 @@
|
||||
"armorMystery201807Notes": "Cette nageoire puissante vous propulsera dans les mers à des vitesses incroyables ! Ne confère aucun bonus. Équipement d'abonné·e de juin 2018.",
|
||||
"armorMystery201808Text": "Armure de dragon de lave",
|
||||
"armorMystery201808Notes": "Cette armure est faite des écailles perdues par l'insaisissable (et très chaud) dragon de lave. Ne confère aucun bonus. Équipement d'abonnement d'Août 2018.",
|
||||
"armorMystery201809Text": "Armor of Autumn Leaves",
|
||||
"armorMystery201809Text": "Armure de feuilles d'automne",
|
||||
"armorMystery201809Notes": "You are not only a small and fearsome leaf puff, you are sporting the most beautiful colors of the season! Confers no benefit. September 2018 Subscriber Item.",
|
||||
"armorMystery301404Text": "Tenue steampunk",
|
||||
"armorMystery301404Notes": "Pimpant et fringuant ! N'apporte aucun bonus. Équipement d'abonné·e de février 3015.",
|
||||
@@ -1022,11 +1022,11 @@
|
||||
"headSpecialSummer2018HealerNotes": "Orné d'aigues-marines, ce fin diadème marque l'autorité sur les gens, les poissons, et celles et ceux qui sont un peu des deux ! Augmente l'intelligence de <%= int %>. Équipement en édition limitée de l'été 2018.",
|
||||
"headSpecialFall2018RogueText": "Alter Ego Face",
|
||||
"headSpecialFall2018RogueNotes": "Most of us hide away our inward struggles. This mask shows that we all experience tension between our good and bad impulses. Plus it comes with a sweet hat! Increases Perception by <%= per %>. Limited Edition 2018 Autumn Gear.",
|
||||
"headSpecialFall2018WarriorText": "Minotaur Visage",
|
||||
"headSpecialFall2018WarriorNotes": "This fearsome mask shows you can really take your tasks by the horns! Increases Strength by <%= str %>. Limited Edition 2018 Autumn Gear.",
|
||||
"headSpecialFall2018MageText": "Candymancer's Hat",
|
||||
"headSpecialFall2018WarriorText": "Visage du minotaure",
|
||||
"headSpecialFall2018WarriorNotes": "Ce masque effrayant montre que vous pouvez vraiment prendre vos tâches par les cornes ! Augmente la Force de <%= str %>. Équipement en édition limitée de l’automne 2018. ",
|
||||
"headSpecialFall2018MageText": "Chapeau de sucromancien",
|
||||
"headSpecialFall2018MageNotes": "This pointy hat is imbued with powerful spells of sweetness. Careful, if it gets wet it may become sticky! Increases Perception by <%= per %>. Limited Edition 2018 Autumn Gear.",
|
||||
"headSpecialFall2018HealerText": "Ravenous Helm",
|
||||
"headSpecialFall2018HealerText": "Heaume vorace",
|
||||
"headSpecialFall2018HealerNotes": "This helm is fashioned from a carnivorous plant renowned for its ability to dispatch zombies and other inconveniences. Just watch out that it doesn't chew on your head. Increases Intelligence by <%= int %>. Limited Edition 2018 Autumn Gear.",
|
||||
"headSpecialGaymerxText": "Heaume de guerrier arc-en-ciel",
|
||||
"headSpecialGaymerxNotes": "En l'honneur de la conférence GaymerX, cet casque spécial est décoré avec un motif arc-en-ciel aussi radieux que coloré ! GaymerX est une convention célébrant les LGBTQ et les jeux, et est ouverte à tous.",
|
||||
@@ -1104,8 +1104,8 @@
|
||||
"headMystery201807Notes": "Les grandes écailles sur ce casque vous protégerons de tout type d'ennemi océanique. Ne confère aucun bonus. Équipement d'abonnement de Juin 2018.",
|
||||
"headMystery201808Text": "Capuche de dragon de lave",
|
||||
"headMystery201808Notes": "Les cornes brillantes sur ce capuchon éclaireront votre chemin à travers les cavernes souterraines. Ne confère aucun bonus. Équipement d'abonnement d'Août 2018.",
|
||||
"headMystery201809Text": "Crown of Autumn Flowers",
|
||||
"headMystery201809Notes": "The last flowers of autumn's warm days are a reminder of the beauty of the season. Confers no benefit. September 2018 Subscriber Item.",
|
||||
"headMystery201809Text": "Couronne de fleurs d'automne",
|
||||
"headMystery201809Notes": "Les dernières fleurs des jours chauds de l'automne sont un rappel de la beauté de cette saison. N'apporte aucun bonus. Équipement d'abonné·e de septembre 2018. ",
|
||||
"headMystery301404Text": "Haut-de-forme fantaisiste",
|
||||
"headMystery301404Notes": "Un couvre-chef fantaisiste pour les gens de bonne famille les plus élégants ! N'apporte aucun bonus. Équipement d'abonné·e de janvier 3015.",
|
||||
"headMystery301405Text": "Haut-de-forme classique",
|
||||
@@ -1364,11 +1364,11 @@
|
||||
"shieldSpecialSummer2018WarriorNotes": "Façonné en pierre, ce redoutable bouclier en forme de crâne inspire la peur aux ennemis poissons tout en rassemblant vos familiers et montures squelettes. Augmente la constitution de <%= con %>. Équipement en édition limitée de l'été 2018.",
|
||||
"shieldSpecialSummer2018HealerText": "Emblème de sirène monarque",
|
||||
"shieldSpecialSummer2018HealerNotes": "Ce bouclier peut produire un dôme d'air au bénéfice des visiteurs terrestres de votre royaume aquatique. Augmente la constitution de <%= con %>Équipement en édition limitée de l'été 2018.. ",
|
||||
"shieldSpecialFall2018RogueText": "Vial of Temptation",
|
||||
"shieldSpecialFall2018RogueText": "Fiole de tentation",
|
||||
"shieldSpecialFall2018RogueNotes": "This bottle represents all the distractions and troubles that keep you from being your best self. Resist! We're cheering for you! Increases Strength by <%= str %>. Limited Edition 2018 Autumn Gear.",
|
||||
"shieldSpecialFall2018WarriorText": "Brilliant Shield",
|
||||
"shieldSpecialFall2018WarriorText": "Bouclier brillant",
|
||||
"shieldSpecialFall2018WarriorNotes": "Super shiny to dissuade any troublesome Gorgons from playing peek-a-boo around the corners! Increases Constitution by <%= con %>. Limited Edition 2018 Autumn Gear.",
|
||||
"shieldSpecialFall2018HealerText": "Hungry Shield",
|
||||
"shieldSpecialFall2018HealerText": "Bouclier affamé",
|
||||
"shieldSpecialFall2018HealerNotes": "With its wide-open maw, this shield will absorb all your enemies' blows. Increases Constitution by <%= con %>. Limited Edition 2018 Autumn Gear.",
|
||||
"shieldMystery201601Text": "Tueuse résolue",
|
||||
"shieldMystery201601Notes": "Cette lame peut être utilisée pour parer toutes les distractions. N'apporte aucun bonus. Équipement d'abonné·e de janvier 2016.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user