Compare commits

..

6 Commits

Author SHA1 Message Date
Phillip Thelen
192d649ffa fix giving gear to contributors 2025-01-23 17:30:31 +01:00
Phillip Thelen
e7aae55eca Give contributors gear immediately 2025-01-21 11:10:29 +01:00
Phillip Thelen
36b03613e1 adjust contributor tests 2025-01-21 11:10:16 +01:00
Phillip Thelen
2de9a16a2c adjust gems per tier 2025-01-21 11:02:19 +01:00
negue
895241b7fa show date tooltip above system / skill messages 2025-01-20 21:08:55 +01:00
negue
2535fd7095 Combined Message Pages/Redesign (#15310)
* split component prepare new views / states

* extract empty and disabled state as components

* fix empty state mail icon

* first logic switching between modes, move page to /private-messages/index.vue

* extract autoCompleteHelper.js

* style header + start new message input

* style plus button + focus input

* state logic, types for sanity

* WIP PM new Message started

* add /members/username test

* first design changes to messageCard

* delete private message or chat - based on the mode

* copy as todo

* mention links to modal

* report chat or private message

* WIP likeButton

* likeButton styling

* hide like on private message cards

* fix unit test

* replace copy as todo - to just a copy to clipboard

* style changes

* menu position + like button width

* dropdown items background + like font

* fix like button padding

* move api endpoints and tests around to group inbox methods  + like for inbox private messages

* restyle system messages

* Dropdown Radius and Padding

* WIP system messages

* fix lint

* copy delta commit of allowing liking own private messages

* enable liking private messages

* fix menu non hovered item icon color

* fix import path

* ignore background on system messages

* requested changes + migration

* update migration to update the unique id to some messages and delete the duplicates

* migration based on users pagination

* fix(migration): use Promise.all

* change to bulkWrites per User, and all messages in one run (of a user)

* check for array

* use rest operator ...

* skip sorting to get the users

* remove migration, disable like for private messages without uniqueMessageId

* lean+bulkWrite for likes, add time checks for like and auth for further debugging

* add a limit 2 get the messages by uniqueId

* Adding a simple server start script

* remove pinned nodemon dep

* fix inbox controller/tests

* fix / requested style changes

* fix empty state padding /

* hide avatar weapons on messages - fix avatar spacing on messages

* Hourglass Simplification (#15323)

* begin removing obsolete tests

* begin refactoring

* update cron tests

* cleanup

* finish basic implementation of new logic

* add more subscription tests

* subscription test improvements

* return nextHourglassDate again

* fix gem limit

* fix(test): short circuit this.

* fix(admin): correct logic and style for shrimple subs

* WIP(frontend): draft of main subs page view

* fix hourglass count

* Fix hourglass logic for upgrades

* fix admin panel display

* WIP(subs): extant Stripe state

* fix admin panel strings

* fix missing transaction type

* add new field for cumulative subscription count

* show date for hourglass bonus if it was received

* fix test

* feat(subscription): max Gems progress readout

* fix(css): correct and refactor heights and selection states

* fix(subs): correct border-radius and redirect

* fix(stripe): correct redirect after success

* Admin panel display fixes

* don’t give additional HG for new sub if they already got one this month

* fix issue with promo hourglasses

* fix(subscription): update layout when gifting

* fix(subscriptions): more gift layout revisions

* fix(subscriptions): minor visual updates

* fix(subs): pass autoRenews through Stripe

* fix(subs): gifts DON't renew

* fix(lint): unnecessary ternary

* fix(lint): do negate object ig

* fix(subs): try again on gifts

* fix(subs): unhovery and un-12-monthy

* fix bug with incorrectly giving HG bonus

* remove only

* fix test

* fix test

* fix(subs): also redirect to subs after gift sub

* fix(subs): fix typeError

* fix(g1g1): don't try to find Gems promo during bogo

---------

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Kalista Payne <sabe@habitica.com>

* chore(sprites): update subproject

* fix(layout): tighten cancellation note

* fix(subs): Google wording and HG escape

* chore(testing): fake g1g1 dates

* fix(subs): don't hide HG preview entirely

* fix(subs): center next hourglass message

* working validatedTextInput.vue within start-new-conversation-input-header.vue 🎉

* fix(git): remove changes from old develop

* Revert "fix(git): remove changes from old develop"

This reverts commit 0e30f7df00.

* fix(git): no actually just this file i guesss

* adding an empty loading state, hiding

* fought the avatar arch nemesis again

* fix chatMessages (party chat) message spacing

* move disabled text back to above the input area - re-enable input area

* show disabled private messages top panel

* fix font color

* fixing uiStates - removing disabled - moving the own user check to the last

* fix(lint): add missing prop defaults

* fix(lint): object default should be fn

* fix(chat): correct grammar in error

---------

Co-authored-by: SabreCat <sabe@habitica.com>
Co-authored-by: Kalista Payne <sabrecat@gmail.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
2025-01-16 16:52:24 -06:00
156 changed files with 2726 additions and 2095 deletions

2
.gitignore vendored
View File

@@ -47,5 +47,5 @@ webpack.webstorm.config
# mongodb replica set for local dev
mongodb-*.tgz
/mongodb-data
/mongodb-data*
/.nyc_output

View File

@@ -2,4 +2,4 @@
This webpage includes the documentation for version 3 of the [Habitica](https://habitica.com) API.
If you're developing a 3rd party tool that uses the Habitica API, read the [API Usage Guidelines](https://github.com/HabitRPG/habitica/wiki/API-Usage-Guidelines), which describe how to be a responsible user of our server resources!
If you're developing a 3rd party tool that uses the Habitica API you should read the [Guidance for Comrades](https://habitica.fandom.com/wiki/Guidance_for_Comrades) and in particular the section called [Rules for Third-Party Tools](https://habitica.fandom.com/wiki/Guidance_for_Comrades#Rules_for_Third-Party_Tools) which includes suggestions on how to best use the API and the rules to follow when interacting with it.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "habitica",
"version": "5.33.0",
"version": "5.32.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
"version": "5.33.0",
"version": "5.32.5",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.33.0",
"version": "5.32.5",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -110,6 +110,7 @@
"start:simple": "node ./website/server/index.js",
"debug": "gulp nodemon --inspect",
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc",
"heroku-postbuild": ".heroku/report_deploy.sh"

View File

@@ -0,0 +1,56 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import common from '../../../../../website/common';
describe('GET /members/username/:username', () => {
let user;
before(async () => {
user = await generateUser();
});
it('validates req.params.username', async () => {
await expect(user.get('/members/username/')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns a member public data only', async () => {
// make sure user has all the fields that can be returned by the getMember call
const member = await generateUser({
contributor: { level: 1 },
backer: { tier: 3 },
preferences: {
costume: false,
background: 'volcano',
},
secret: {
text: 'Clark Kent',
},
});
const memberRes = await user.get(`/members/username/${member.auth.local.username}`);
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
]);
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
].sort());
expect(memberRes.stats.maxMP).to.exist;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
expect(memberRes.inbox.optOut).to.exist;
expect(memberRes.inbox.canReceive).to.exist;
expect(memberRes.inbox.messages).to.not.exist;
expect(memberRes.secret).to.not.exist;
expect(memberRes.blocks).to.not.exist;
});
});

View File

@@ -0,0 +1,104 @@
import find from 'lodash/find';
import {
generateUser,
translate as t,
} from '../../../helpers/api-integration/v4';
/**
* Checks the messages array if the uniqueMessageId has the like flag
* @param {InboxMessage[]} messages
* @param {String} uniqueMessageId
* @param {String} userId
* @param {Boolean} likeStatus
*/
function expectMessagesLikeStatus (messages, uniqueMessageId, userId, likeStatus) {
const messageToCheck = find(messages, { uniqueMessageId });
expect(messageToCheck.likes[userId]).to.equal(likeStatus);
}
// eslint-disable-next-line mocha/no-exclusive-tests
describe('POST /inbox/like-private-message/:messageId', () => {
let userToSendMessage;
const getLikeUrl = messageId => `/inbox/like-private-message/${messageId}`;
before(async () => {
userToSendMessage = await generateUser();
});
it('Returns an error when private message is not found', async () => {
await expect(userToSendMessage.post(getLikeUrl('some-unknown-id')))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('Likes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const senderMessages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(senderMessages, uniqueMessageId, receiver._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, receiver._id, true);
});
it('Allows to likes their own private message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await userToSendMessage.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[userToSendMessage._id]).to.equal(true);
const messages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(messages, uniqueMessageId, userToSendMessage._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, userToSendMessage._id, true);
});
it('Unlikes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const unlikeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(unlikeResult.likes[receiver._id]).to.equal(false);
const messages = await userToSendMessage.get('/inbox/messages');
const messageToCheck = find(messages, { id: sentMessageResult.message.id });
expect(messageToCheck.likes[receiver._id]).to.equal(false);
});
});

View File

@@ -7,7 +7,7 @@ module.exports = {
extends: [
'habitrpg/lib/vue',
],
ignorePatterns: ['dist/', 'node_modules/'],
ignorePatterns: ['dist/', 'node_modules/', '*.d.ts'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',

View File

@@ -1730,11 +1730,6 @@
width: 141px;
height: 147px;
}
.background_old_fashioned_tea_shop {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_old_fashioned_tea_shop.png');
width: 141px;
height: 147px;
}
.background_old_photo {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_old_photo.png');
width: 141px;
@@ -30219,11 +30214,6 @@
width: 90px;
height: 90px;
}
.head_armoire_fancyFloralHat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_fancyFloralHat.png');
width: 114px;
height: 90px;
}
.head_armoire_fancyPirateHat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_fancyPirateHat.png');
width: 114px;
@@ -30654,11 +30644,6 @@
width: 114px;
height: 90px;
}
.shield_armoire_fancyFloralFan {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_fancyFloralFan.png');
width: 114px;
height: 90px;
}
.shield_armoire_fancyShoe {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_fancyShoe.png');
width: 90px;
@@ -33734,6 +33719,16 @@
width: 90px;
height: 90px;
}
.head_mystery_202501 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202501.png');
width: 114px;
height: 90px;
}
.shield_mystery_202501 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202501.png');
width: 114px;
height: 90px;
}
.back_mystery_201402 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_201402.png');
width: 90px;
@@ -35409,36 +35404,6 @@
width: 114px;
height: 90px;
}
.head_mystery_202501 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202501.png');
width: 114px;
height: 90px;
}
.shield_mystery_202501 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202501.png');
width: 114px;
height: 90px;
}
.broad_armor_mystery_202502 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202502.png');
width: 114px;
height: 90px;
}
.head_mystery_202502 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202502.png');
width: 114px;
height: 90px;
}
.shield_mystery_202502 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202502.png');
width: 114px;
height: 90px;
}
.slim_armor_mystery_202502 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202502.png');
width: 114px;
height: 90px;
}
.broad_armor_mystery_301404 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png');
width: 90px;
@@ -40187,66 +40152,6 @@
width: 28px;
height: 28px;
}
.notif_armor_special_birthday {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2015 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2015.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2016 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2016.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2017 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2017.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2018 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2018.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2019 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2019.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2020 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2020.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2021 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2021.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2022 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2022.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2023 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2023.png');
width: 28px;
height: 28px;
}
.notif_armor_special_birthday2024 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_armor_special_birthday2024.png');
width: 28px;
height: 28px;
}
.notif_cake_birthday {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_cake_birthday.png');
width: 28px;
height: 28px;
}
.notif_candy_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_candy_nye.png');
width: 28px;
@@ -40652,11 +40557,6 @@
width: 219px;
height: 219px;
}
.quest_cat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_cat.png');
width: 219px;
height: 219px;
}
.quest_chameleon {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_chameleon.png');
width: 216px;
@@ -41392,11 +41292,6 @@
width: 68px;
height: 68px;
}
.inventory_quest_scroll_cat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_cat.png');
width: 68px;
height: 68px;
}
.inventory_quest_scroll_chameleon {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_chameleon.png');
width: 68px;
@@ -43002,56 +42897,6 @@
width: 105px;
height: 105px;
}
.Mount_Body_Cat-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-Base.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-CottonCandyBlue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-CottonCandyBlue.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-CottonCandyPink {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-CottonCandyPink.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-Desert {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-Desert.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-Golden {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-Golden.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-Red {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-Red.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-Shade {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-Shade.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-Skeleton {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-Skeleton.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-White {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-White.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cat-Zombie {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cat-Zombie.png');
width: 105px;
height: 105px;
}
.Mount_Body_Chameleon-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Base.png');
width: 105px;
@@ -48592,56 +48437,6 @@
width: 105px;
height: 105px;
}
.Mount_Head_Cat-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-Base.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-CottonCandyBlue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-CottonCandyBlue.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-CottonCandyPink {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-CottonCandyPink.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-Desert {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-Desert.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-Golden {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-Golden.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-Red {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-Red.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-Shade {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-Shade.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-Skeleton {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-Skeleton.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-White {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-White.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cat-Zombie {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cat-Zombie.png');
width: 105px;
height: 105px;
}
.Mount_Head_Chameleon-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Base.png');
width: 105px;
@@ -54242,56 +54037,6 @@
width: 81px;
height: 99px;
}
.Pet-Cat-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-Base.png');
width: 81px;
height: 99px;
}
.Pet-Cat-CottonCandyBlue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-CottonCandyBlue.png');
width: 81px;
height: 99px;
}
.Pet-Cat-CottonCandyPink {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-CottonCandyPink.png');
width: 81px;
height: 99px;
}
.Pet-Cat-Desert {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-Desert.png');
width: 81px;
height: 99px;
}
.Pet-Cat-Golden {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-Golden.png');
width: 81px;
height: 99px;
}
.Pet-Cat-Red {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-Red.png');
width: 81px;
height: 99px;
}
.Pet-Cat-Shade {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-Shade.png');
width: 81px;
height: 99px;
}
.Pet-Cat-Skeleton {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-Skeleton.png');
width: 81px;
height: 99px;
}
.Pet-Cat-White {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-White.png');
width: 81px;
height: 99px;
}
.Pet-Cat-Zombie {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cat-Zombie.png');
width: 81px;
height: 99px;
}
.Pet-Chameleon-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Base.png');
width: 81px;

View File

@@ -101,8 +101,7 @@
.btn-secondary,
.dropdown > .btn-secondary.dropdown-toggle:not(.btn-success),
.show > .btn-secondary.dropdown-toggle:not(.btn-success)
{
.show > .btn-secondary.dropdown-toggle:not(.btn-success) {
background: $white;
border: 2px solid transparent;
color: $gray-50;
@@ -298,6 +297,16 @@
box-shadow: none;
}
.btn-flat,
.dropdown > .btn-flat.dropdown-toggle:not(.btn-success),
.show > .btn-flat.dropdown-toggle:not(.btn-success) {
&.with-icon {
.svg-icon.color {
color: var(--icon-color);
}
}
}
.btn-cancel {
color: $blue-10;
}

View File

@@ -38,7 +38,12 @@
border-radius: 2px;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
padding: 0;
}
.no-min-width {
.dropdown-menu {
min-width: 0 !important;
}
}
// shared dropdown-item styles
@@ -54,6 +59,8 @@
color: $gray-50 !important;
cursor: pointer;
--dropdown-item-hover-icon-color: #{$gray-200};
&:focus {
outline: none;
background-color: inherit;
@@ -88,7 +95,7 @@
&:not(:hover) {
.with-icon .svg-icon {
color: $gray-200;
color: var(dropdown-item-hover-icon-color);
}
}
}
@@ -151,7 +158,7 @@
// selectList.vue items sizing
.selectListItem .dropdown-item {
padding: 0.25rem 0.75rem;
padding: 0.25rem 1rem 0.25rem 0.75rem;
height: 32px;
&:active, &:hover, &:focus, &.active {

View File

@@ -1,3 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M4,0C1.79,0,0,1.79,0,4v16c0,2.21,1.79,4,4,4h16c2.21,0,4-1.79,4-4V4c0-2.21-1.79-4-4-4H4ZM12,11.57c-.72-1.49-2.7-4.26-4.53-5.63-1.32-.99-3.47-1.75-3.47.68,0,.49.28,4.08.44,4.66.57,2.03,2.65,2.55,4.5,2.23-3.24.55-4.06,2.36-2.28,4.17,3.38,3.44,4.85-.86,5.23-1.97h0s0,0,0,0c.07-.2.1-.29.1-.21,0-.08.03.01.1.22h0c.38,1.1,1.85,5.41,5.23,1.97,1.78-1.81.95-3.63-2.28-4.17,1.85.31,3.93-.2,4.5-2.23.16-.58.44-4.18.44-4.66,0-2.43-2.14-1.67-3.47-.68-1.83,1.37-3.81,4.14-4.53,5.63Z" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 572 B

View File

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

After

Width:  |  Height:  |  Size: 622 B

View File

@@ -25,9 +25,9 @@
<router-link to="/">
Homepage
</router-link>or
<a href="mailto:admin@habitica.com">
<router-link :to="contactUsLink">
Contact Us
</a>about the issue.
</router-link>about the issue.
</p>
</div>
</div>
@@ -40,6 +40,12 @@ import { mapState } from '@/libs/store';
export default {
computed: {
...mapState(['isUserLoggedIn']),
contactUsLink () {
if (this.isUserLoggedIn) {
return { name: 'guild', params: { groupId: 'a29da26b-37de-4a71-b0c6-48e72a900dac' } };
}
return { name: 'contact' };
},
retiredChatPage () {
return this.$route.fullPath.indexOf('/groups') !== -1;
},

View File

@@ -37,9 +37,9 @@
<h3>{{ $t('footerCompany') }}</h3>
<ul>
<li>
<a href="mailto:admin@habitica.com">
<router-link to="/static/contact">
{{ $t('contactUs') }}
</a>
</router-link>
</li>
<li>
<router-link to="/static/press-kit">
@@ -205,12 +205,12 @@
</a>
<a
class="social-circle"
href="https://bsky.app/profile/habitica.com"
href="https://twitter.com/habitica/"
target="_blank"
>
<div
class="social-icon svg-icon bluesky"
v-html="icons.bluesky"
class="social-icon svg-icon twitter"
v-html="icons.twitter"
></div>
</a>
<a
@@ -807,7 +807,7 @@ h3 {
}
}
.bluesky svg {
.twitter svg {
background-color: #e1e0e3;
fill: #878190;
height: 24px;
@@ -846,7 +846,7 @@ import Vue from 'vue';
// images
import melior from '@/assets/svg/melior.svg';
import bluesky from '@/assets/svg/bluesky.svg';
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
import instagram from '@/assets/svg/instagram.svg';
import tumblr from '@/assets/svg/tumblr.svg';
@@ -878,7 +878,7 @@ export default {
return {
icons: Object.freeze({
melior,
bluesky,
twitter,
facebook,
instagram,
tumblr,
@@ -920,13 +920,13 @@ export default {
await axios.post('/api/v4/debug/set-cron', {
lastCron: date,
});
window.alert(`Days reset by ${numberOfDays}.\nRemember to refresh.`);
// @TODO: Notification.text('-' + numberOfDays + ' day(s), remember to refresh');
// @TODO: Sync user?
},
async addTenGems () {
await axios.post('/api/v4/debug/add-ten-gems');
// @TODO: Notification.text('+10 Gems!');
this.user.balance += 2.5;
window.alert('+10 Gems!');
},
async addHourglass () {
await axios.post('/api/v4/debug/add-hourglass');
@@ -990,17 +990,17 @@ export default {
},
async addQuestProgress () {
await axios.post('/api/v4/debug/quest-progress');
window.alert('Quest progress increased.');
// @TODO: Notification.text('Quest progress increased');
// @TODO: User.sync();
},
async bossRage () {
await axios.post('/api/v4/debug/boss-rage');
window.alert('Boss Rage increased.');
},
async makeAdmin () {
await axios.post('/api/v4/debug/make-admin');
window.alert('You are now an Admin!\nReload the website then go to Help > Admin to set contributor level, etc.');
// @TODO: Notification.text('You are now an admin!
// Reload the website then go to Help > Admin Panel to set contributor level, etc.');
// @TODO: sync()
},
donate () {

View File

@@ -1,9 +1,10 @@
<template>
<div class="avatar-wrapper">
<div
v-if="member.preferences"
class="avatar"
:style="{width, height, paddingTop}"
:class="backgroundClass"
:class="topLevelClassList"
@click.prevent="castEnd()"
>
<div
@@ -55,7 +56,11 @@
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
<span :class="[getGearClass('head'), specialMountClass]"></span>
<span :class="[getGearClass('headAccessory'), specialMountClass]"></span>
<span :class="['hair_flower_' + member.preferences.hair.flower, specialMountClass]"></span>
<span
:class="[
'hair_flower_' + member.preferences.hair.flower, specialMountClass
]"
></span>
<span
v-if="!hideGear('shield')"
:class="[getGearClass('shield'), specialMountClass]"
@@ -63,6 +68,7 @@
<span
v-if="!hideGear('weapon')"
:class="[getGearClass('weapon'), specialMountClass]"
class="weapon"
></span>
</template>
<!-- Resting-->
@@ -89,6 +95,7 @@
:member-class="member.stats.class"
/>
</div>
</div>
</template>
<style lang="scss" scoped>
@@ -96,15 +103,23 @@
.avatar {
width: 141px;
height: 147px;
image-rendering: pixelated;
position: relative;
cursor: pointer;
&.centered-avatar {
margin: 0 auto;
}
// resetting the additional padding
margin-bottom: -0.5rem !important;
}
.character-sprites {
width: 90px;
height: 90px;
display: inline-flex;
}
.character-sprites span {
@@ -123,6 +138,27 @@
.invert {
filter: invert(100%);
}
.weapon {
// the only one that is relative so that it fits into the parent div
position: relative !important;
}
.debug {
border: 1px solid red;
.character-sprites {
border: 1px solid blue;
}
.weapon {
border: 1px solid green;
}
span {
border: 1px solid yellow;
}
}
</style>
<script>
@@ -133,12 +169,24 @@ import foolPet from '../mixins/foolPet';
import ClassBadge from '@/components/members/classBadge';
/**
* TODO replace avatarOnly with multiple options like
* - showMount
* - showPet
* - showBackground
* - showWeapons
*/
export default {
components: {
ClassBadge,
},
mixins: [foolPet],
props: {
debugMode: {
type: Boolean,
default: false,
},
member: {
type: Object,
required: true,
@@ -156,14 +204,21 @@ export default {
},
overrideAvatarGear: {
type: Object,
default (data) {
return data;
},
},
width: {
type: Number,
default: 140,
type: String,
default: '140px',
},
height: {
type: Number,
default: 147,
type: String,
default: undefined,
},
centerAvatar: {
type: Boolean,
default: false,
},
spritesMargin: {
type: String,
@@ -171,11 +226,16 @@ export default {
},
overrideTopPadding: {
type: String,
default: null,
},
showVisualBuffs: {
type: Boolean,
default: true,
},
showWeapon: {
type: Boolean,
default: true,
},
},
computed: {
...mapState({
@@ -204,6 +264,19 @@ export default {
return val;
},
topLevelClassList () {
const classes = [this.backgroundClass];
if (this.debugMode) {
classes.push('debug');
}
if (this.centerAvatar) {
classes.push('centered-avatar');
}
return classes.join(' ');
},
backgroundClass () {
if (this.member) {
const { background } = this.member.preferences;
@@ -290,6 +363,10 @@ export default {
},
hideGear (gearType) {
if (!this.member) return true;
if (!this.showWeapon) {
return true;
}
if (gearType === 'weapon') {
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];

View File

@@ -1,352 +0,0 @@
<template>
<div>
<div
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="hasPermission(user, 'moderator') && msg.flagCount"
class="message-hidden"
>
{{ flagCountDescription }}
</div>
<div class="card-body">
<user-link
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span>
<span
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">({{ msg.client }})</span>
</p>
<div
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
></div>
<hr>
<div
v-if="msg.id"
class="d-flex"
>
<div
class="action d-flex align-items-center"
@click="copyAsTodo(msg)"
>
<div
class="svg-icon"
v-html="icons.copy"
></div>
<div>{{ $t('copyAsTodo') }}</div>
</div>
<div
v-if="(user.flags.communityGuidelinesAccepted && msg.uuid !== 'system')
&& (!isMessageReported || hasPermission(user, 'moderator'))"
class="action d-flex align-items-center"
@click="report(msg)"
>
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
</div>
<div
v-if="msg.uuid === user._id || hasPermission(user, 'moderator')"
class="action d-flex align-items-center"
@click="remove()"
>
<div
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div v-once>
{{ $t('delete') }}
</div>
</div>
<div
v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}"
class="ml-auto d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('liked')"
v-html="icons.liked"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="!msg.likes[user._id]">{{ $t('like') }}</span>
</div>
</div>
</div>
</template>
<style lang="scss">
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
}
.at-text {
color: #6133b4;
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #bda8ff;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.card-body {
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
.time {
font-size: 12px;
color: #878190;
margin-bottom: 0.5rem;
}
.text {
font-size: 14px;
color: #4e4a57;
text-align: initial;
min-height: 0rem;
}
}
.action {
display: inline-block;
color: #878190;
margin-right: 1em;
font-size: 12px;
:hover {
cursor: pointer;
}
.svg-icon {
color: #A5A1AC;
margin-right: .2em;
width: 16px;
}
}
.activeLike {
color: $purple-300;
.svg-icon {
color: $purple-400;
}
}
</style>
<script>
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
import renderWithMentions from '@/libs/renderWithMentions';
import { userStateMixin } from '../../mixins/userState';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import copyIcon from '@/assets/svg/copy.svg';
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
import reportIcon from '@/assets/svg/report.svg';
export default {
components: { userLink },
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
mixins: [userStateMixin],
props: {
msg: {},
groupId: {},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
copy: copyIcon,
report: reportIcon,
delete: deleteIcon,
liked: likedIcon,
}),
reported: false,
};
},
computed: {
isUserMentioned () {
const message = this.msg;
if (message.highlight) return true;
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
flagCountDescription () {
if (!this.msg.flagCount) return '';
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) return 'Message flagged once, not hidden';
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) return 'Message hidden';
return 'Message hidden (shadow-muted)';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
},
mounted () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
this.$emit('chat-card-mounted', this.msg.id);
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatId: message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
copyAsTodo (message) {
this.$root.$emit('habitica::copy-as-todo', message);
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
this.reported = true;
}
this.$root.$off('habitica:report-result');
});
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: this.groupId || 'privateMessage',
});
},
async remove () {
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
const message = this.msg;
this.$emit('message-removed', message);
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);
},
},
};
</script>

View File

@@ -3,15 +3,6 @@
ref="container"
class="container-fluid"
>
<div class="row">
<div class="col-12">
<copy-as-todo-modal
:group-type="groupType"
:group-name="groupName"
:group-id="groupId"
/>
</div>
</div>
<div class="row loadmore">
<div v-if="canLoadMore">
<div class="loadmore-divider"></div>
@@ -33,6 +24,8 @@
<div
v-for="msg in messages.filter(m => chat && canViewFlag(m))"
:key="msg.id"
class="message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div class="d-flex">
<avatar
@@ -45,16 +38,14 @@
:override-top-padding="'14px'"
@click.native="showMemberModal(msg.uuid)"
/>
<div class="card">
<chat-card
<message-card
:msg="msg"
:group-id="groupId"
:user-sent-message="user._id === msg.uuid"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@chat-card-mounted="itemWasMounted"
@message-card-mounted="itemWasMounted"
/>
</div>
<avatar
v-if="user._id === msg.uuid"
:class="{ invisible: avatarUnavailable(msg) }"
@@ -137,11 +128,27 @@
margin-bottom: .5em;
padding: 0rem;
width: 90%;
&.system-message {
width: 100%;
}
}
.message-scroll .d-flex {
min-width: 1px;
}
.message-row {
margin-left: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
}
}
}
</style>
<script>
@@ -152,13 +159,13 @@ import findIndex from 'lodash/findIndex';
import { userStateMixin } from '../../mixins/userState';
import Avatar from '../avatar';
import copyAsTodoModal from './copyAsTodoModal';
import chatCard from './chatCard';
import MessageCard from '@/components/messages/messageCard.vue';
// TODO merge chatMessages.vue (party message list) with messageList.vue (private message list)
export default {
components: {
copyAsTodoModal,
chatCard,
MessageCard,
Avatar,
},
mixins: [userStateMixin],

View File

@@ -1,105 +0,0 @@
<template>
<b-modal
id="copyAsTodo"
:title="$t('copyMessageAsToDo')"
:hide-footer="true"
size="md"
>
<div class="form-group">
<input
v-model="task.text"
class="form-control"
type="text"
>
</div>
<div class="form-group">
<textarea
v-model="task.notes"
class="form-control"
rows="5"
focus-element="true"
></textarea>
</div>
<hr>
<task
v-if="task._id"
:is-user="isUser"
:task="task"
/>
<div class="modal-footer">
<button
class="btn btn-secondary"
@click="close()"
>
{{ $t('close') }}
</button>
<button
class="btn btn-primary"
@click="saveTodo()"
>
{{ $t('submit') }}
</button>
</div>
</b-modal>
</template>
<script>
import taskDefaults from '@/../../common/script/libs/taskDefaults';
import { mapActions } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import notificationsMixin from '@/mixins/notifications';
import Task from '@/components/tasks/task';
const baseUrl = 'https://habitica.com';
export default {
directives: {
markdown: markdownDirective,
},
components: {
Task,
},
mixins: [notificationsMixin],
props: ['copyingMessage', 'groupType', 'groupName', 'groupId'],
data () {
return {
isUser: true,
task: {},
};
},
mounted () {
this.$root.$on('habitica::copy-as-todo', message => {
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${this.groupPath()})`;
const newTask = {
text: message.text,
type: 'todo',
notes,
};
this.task = taskDefaults(newTask, this.$store.state.user.data);
this.$root.$emit('bv::show::modal', 'copyAsTodo');
});
},
beforeDestroy () {
this.$root.$off('habitica::copy-as-todo');
},
methods: {
...mapActions({
createTask: 'tasks:create',
}),
groupPath () {
if (this.groupType === 'party') {
return `${baseUrl}/party`;
}
return `${baseUrl}/groups/guild/${this.groupId}`;
},
close () {
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
saveTodo () {
this.createTask(this.task);
this.text(this.$t('messageAddedAsToDo'));
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
},
};
</script>

View File

@@ -1177,7 +1177,7 @@ export default {
'flags.welcomed': true,
});
// NOTE: This is a timeout to ensure dom is loaded
// @TODO: This is a timeout to ensure dom is loaded
window.setTimeout(() => {
this.initTour();
this.goto('intro', 0);

View File

@@ -22,13 +22,13 @@
:placeholder="placeholder"
:class="{'user-entry': newMessage}"
:maxlength="MAX_MESSAGE_LENGTH"
@keydown="updateCarretPosition"
@keydown="autoCompleteMixinUpdateCarretPosition"
@keyup.ctrl.enter="sendMessageShortcut()"
@keydown.tab="handleTab($event)"
@keydown.up="selectPreviousAutocomplete($event)"
@keydown.down="selectNextAutocomplete($event)"
@keypress.enter="selectAutocomplete($event)"
@keydown.esc="handleEscape($event)"
@keydown.tab="autoCompleteMixinHandleTab($event)"
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
@keydown.esc="autoCompleteMixinHandleEscape($event)"
@paste="disableMessageSendShortcut()"
></textarea>
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
@@ -36,8 +36,8 @@
ref="autocomplete"
:text="newMessage"
:textbox="textbox"
:coords="coords"
:caret-position="caretPosition"
:coords="mixinData.autoComplete.coords"
:caret-position="mixinData.autoComplete.caretPosition"
:chat="group.chat"
@select="selectedAutocomplete"
/>
@@ -74,7 +74,7 @@
<slot name="additionRow"></slot>
<div class="row">
<div class="hr col-12"></div>
<chat-message
<chat-messages
:chat.sync="group.chat"
:group-type="group.type"
:group-id="group._id"
@@ -86,16 +86,15 @@
</template>
<script>
import debounce from 'lodash/debounce';
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines';
import chatMessage from '../chat/chatMessages';
import chatMessages from '../chat/chatMessages';
import { mapState } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
export default {
directives: {
@@ -104,23 +103,18 @@ export default {
components: {
autocomplete,
communityGuidelines,
chatMessage,
chatMessages,
},
mixins: [externalLinks],
mixins: [externalLinks, autoCompleteHelperMixin],
props: ['label', 'group', 'placeholder'],
data () {
return {
newMessage: '',
sending: false,
caretPosition: 0,
chat: {
submitDisable: false,
submitTimeout: null,
},
coords: {
TOP: 0,
LEFT: 0,
},
textbox: null,
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
};
@@ -142,35 +136,6 @@ export default {
this.handleExternalLinks();
},
methods: {
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {
this.caretPosition = text.selectionEnd;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, this.caretPosition);
span.textContent = text.value.substr(this.caretPosition) || '.';
div.appendChild(span);
this.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
async sendMessageShortcut () {
// If the user recently pasted in the text field, don't submit
if (!this.chat.submitDisable) {
@@ -221,50 +186,6 @@ export default {
}, 500);
},
handleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
handleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
selectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
selectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
selectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
selectedAutocomplete (newText, newCaret) {
this.newMessage = newText;
// Wait for v-modal to update
@@ -273,7 +194,6 @@ export default {
this.textbox.focus();
});
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
@@ -284,10 +204,7 @@ export default {
beforeRouteUpdate (to, from, next) {
// Reset chat
this.newMessage = '';
this.coords = {
TOP: 0,
LEFT: 0,
};
this.autoCompleteMixinResetCoordsPosition();
next();
},

View File

@@ -28,7 +28,6 @@
:name="member.profile.name"
:backer="member.backer"
:contributor="member.contributor"
:smaller-style="true"
/>
<inline-class-badge
v-if="member.stats"

View File

@@ -0,0 +1,110 @@
<template>
<div
class="d-inline-flex like-button"
@click="like()"
>
<div
v-b-tooltip="{title: likeTooltip(likeCount)}"
class="d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{isLiked: true, currentUserLiked: likedByCurrentUser}"
>
<div
class="svg-icon mr-1"
:title="$t('liked')"
v-html="icons.liked"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-1"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="likeCount === 0">{{ $t('like') }}</span>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.action {
display: inline-block;
margin-right: 1em;
.svg-icon {
color: $gray-100;
width: 16px;
}
&.isLiked {
color: $purple-200;
font-weight: bold;
.svg-icon {
color: $purple-300;
}
}
}
.like-button {
color: $gray-100;
font-size: 12px;
line-height: 16px;
&:hover {
cursor: pointer;
color: $purple-200;
.svg-icon {
color: $purple-300;
}
}
}
</style>
<script>
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
export default {
props: {
likeCount: {
type: Number,
},
likedByCurrentUser: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
liked: likedIcon,
}),
};
},
methods: {
async like () {
this.$emit('toggle-like');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
},
};
</script>

View File

@@ -1,12 +1,44 @@
<template>
<div class="card-body">
<div
class="card"
:class="{
'system-message': isSystemMessage
}"
>
<div
v-b-tooltip.hover="messageDateForSystemMessage"
class="message-card"
:class="{
'user-sent-message': userSentMessage,
'user-received-message': !userSentMessage && !isSystemMessage,
'system-message': isSystemMessage
}"
>
<div
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="userIsModerator && msg.flagCount"
class="message-hidden"
>
{{ flagCountDescription }}
</div>
<div
class="card-body"
>
<user-link
v-if="!isSystemMessage"
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<p
v-if="!isSystemMessage"
class="time"
>
<span
v-if="msg.username"
class="mr-1"
@@ -14,12 +46,86 @@
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4"> ({{ msg.client }})</span>
<span v-b-tooltip.hover="messageDate">{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">
({{ msg.client }})
</span>
</p>
<b-dropdown
v-if="!isSystemMessage"
right="right"
variant="flat"
toggle-class="with-icon"
class="card-menu no-min-width"
:no-caret="true"
>
<template #button-content>
<span
v-once
class="svg-icon inline menuIcon color"
v-html="icons.menuIcon"
>
</span>
</template>
<b-dropdown-item
class="selectListItem"
@click="copy(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.copy"
></span>
<span v-once>
{{ $t('copy') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canReportMessage"
class="selectListItem custom-hover--red"
@click="report(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.report"
></span>
<span v-once>
{{ $t('report') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canDeleteMessage"
class="selectListItem custom-hover--red"
@click="remove()"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.delete"
></span>
<span v-once>
{{ $t('delete') }}
</span>
</span>
</b-dropdown-item>
</b-dropdown>
<div
v-if="isSystemMessage"
class="system-message-body"
>
{{ msg.unformattedText }}
</div>
<div
v-else
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
@@ -31,43 +137,21 @@
<span v-once>{{ $t('reportedMessage') }}</span><br>
<span v-once>{{ $t('canDeleteNow') }}</span>
</div>
<hr>
<div
v-if="msg.id"
class="d-flex"
>
<div
v-if="!isMessageReported"
class="action d-flex align-items-center"
@click="report(msg)"
>
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
</div>
<div
class="action d-flex align-items-center"
@click="remove()"
>
<div
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div v-once>
{{ $t('delete') }}
</div>
<like-button
v-if="canLikeMessage"
class="mt-75"
:liked-by-current-user="msg.likes[user._id]"
:like-count="likeCount"
@toggle-like="like()"
/>
</div>
</div>
</div>
</template>
<style lang="scss">
.message-card {
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
@@ -76,27 +160,50 @@
.at-text {
color: #6133b4;
}
.card-menu button {
justify-content: center;
margin: 0;
padding: 0;
height: 1rem;
width: 1rem;
}
.markdown p:last-of-type {
margin-bottom: 0;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.action {
display: inline-block;
color: $gray-200;
margin-right: 1em;
font-size: 12px;
:hover {
cursor: pointer;
.card {
background: transparent !important;
margin-bottom: 0 !important;
}
.svg-icon {
color: $gray-300;
margin-right: .2em;
.message-card:not(.system-message) {
background: white;
}
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: $purple-500;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
.active {
@@ -107,12 +214,22 @@
}
}
.message-card {
border-radius: 7px;
margin: 0;
padding: 1rem 0.75rem 0.5rem 1rem;
&.system-message {
padding-top: 0.5rem;
}
.card-body {
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
position: relative;
padding: 0;
.time {
font-size: 12px;
color: $gray-200;
color: $gray-100;
margin-bottom: 0.5rem;
}
@@ -123,6 +240,23 @@
min-height: 0rem;
}
}
}
.card-menu {
position: absolute;
top: 0;
right: 0;
&:not(.show) {
display: none;
}
}
.card-body:hover {
.card-menu {
display: block;
}
}
hr {
margin-bottom: 0.5rem;
@@ -133,39 +267,146 @@
margin-top: 18px;
color: $red-50;
}
.selectListItem:not(:hover) .svg-icon.icon-16.color {
color: #{$gray-100}
}
.custom-hover--red {
--hover-color: #{$maroon-50};
--hover-background: #{rgba($red-500, 0.25)};
}
.user-sent-message {
border: 1px solid $purple-400;
}
.system-message {
border: 1px solid $purple-400;
}
.user-received-message {
border: 1px solid $gray-500;
}
.card-menu {
// icon-color is the menu icon itself
--icon-color: #{$gray-100};
--dropdown-item-hover-icon-color: #{$gray-100};
&:hover {
--icon-color: #{$purple-300};
}
}
.menuIcon {
width: 4px;
height: 1rem;
object-fit: contain;
}
.system-message-body {
line-height: 1.71;
text-align: center;
color: $purple-300;
}
</style>
<script>
import axios from 'axios';
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_FROM_SHADOW_MUTE, CHAT_FLAG_LIMIT_FOR_HIDING } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import { CopyToClipboardMixin } from '@/mixins/copyToClipboard';
import renderWithMentions from '@/libs/renderWithMentions';
import { mapState } from '@/libs/store';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import reportIcon from '@/assets/svg/report.svg';
import menuIcon from '@/assets/svg/menu.svg';
import { userStateMixin } from '@/mixins/userState';
import copyIcon from '@/assets/svg/copy.svg';
import LikeButton from '@/components/messages/likeButton.vue';
const LikeLogicMixin = {
computed: {
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatMessageId: this.privateMessageMode ? message.uniqueMessageId : message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
},
};
export default {
components: {
LikeButton,
userLink,
},
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
mixins: [externalLinks],
},
mixins: [
externalLinks, userStateMixin, LikeLogicMixin,
CopyToClipboardMixin,
],
props: {
msg: {},
msg: {
type: Object,
},
groupId: {
type: String,
},
privateMessageMode: {
type: Boolean,
},
userSentMessage: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
delete: deleteIcon,
report: reportIcon,
copy: copyIcon,
menuIcon,
}),
reported: false,
};
@@ -175,19 +416,100 @@ export default {
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
messageDateForSystemMessage () {
return this.isSystemMessage ? this.messageDate : '';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
userIsModerator () {
return this.hasPermission(this.user, 'moderator');
},
isSystemMessage () {
return this.msg.uuid === 'system';
},
canLikeMessage () {
if (this.isSystemMessage) {
return false;
}
if (this.privateMessageMode) {
return Boolean(this.msg.uniqueMessageId);
}
return this.msg.id;
},
canDeleteMessage () {
return this.privateMessageMode
|| this.msg.uuid === this.user._id
|| this.userIsModerator;
},
canReportMessage () {
if (this.privateMessageMode) {
return !this.isMessageReported;
}
return (this.user.flags.communityGuidelinesAccepted && this.msg.uuid !== 'system')
&& (!this.isMessageReported || this.userIsModerator);
},
isUserMentioned () {
const message = this.msg;
if (message.highlight) {
return true;
}
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
flagCountDescription () {
if (!this.msg.flagCount) {
return '';
}
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) {
return 'Message flagged once, not hidden';
}
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) {
return 'Message hidden';
}
return 'Message hidden (shadow-muted)';
},
},
mounted () {
this.$emit('message-card-mounted');
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
updated () {
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
methods: {
mapProfileLinksToModal () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
@@ -199,16 +521,29 @@ export default {
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: 'privateMessage',
groupId: this.groupId,
});
},
async remove () {
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
// eslint-disable-next-line no-alert
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) {
return;
}
const message = this.msg;
this.$emit('message-removed', message);
if (this.privateMessageMode) {
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
} else {
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
}
},
copy (message) {
this.mixinCopyToClipboard(message.text, this.$t('messageCopiedToClipboard'));
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);

View File

@@ -1,9 +1,9 @@
<template>
<div
ref="container"
class="container-fluid"
class="message-list"
>
<div class="row loadmore">
<div class="loadmore">
<div v-if="canLoadMore && !isLoading">
<div class="loadmore-divider-holder">
<div class="loadmore-divider"></div>
@@ -28,7 +28,7 @@
<div
v-for="(msg) in messages"
:key="msg.id"
class="row message-row"
class="message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div
@@ -39,28 +39,31 @@
class="avatar-left"
:member="conversationOpponentUser"
:avatar-only="true"
:override-top-padding="'14px'"
:show-weapon="false"
:debug-mode="false"
:override-top-padding="'0'"
:hide-class-badge="true"
@click.native="showMemberModal(msg.uuid)"
/>
<div
class="card"
:class="{'card-right': user._id !== msg.uuid, 'card-left': user._id === msg.uuid}"
>
<message-card
:msg="msg"
:user-sent-message="user._id === msg.uuid"
:group-id="'privateMessage'"
:private-message-mode="true"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@message-card-mounted="itemWasMounted"
/>
</div>
<avatar
v-if="user && user._id === msg.uuid"
class="avatar-right"
:member="user"
:avatar-only="true"
:show-weapon="false"
:debug-mode="false"
:hide-class-badge="true"
:override-top-padding="'14px'"
:override-top-padding="'0'"
@click.native="showMemberModal(msg.uuid)"
/>
</div>
@@ -71,15 +74,28 @@
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.avatar {
width: 170px;
min-width: 8rem;
height: 120px;
padding-top: 0 !important;
.avatar-left, .avatar-right {
align-self: center;
::v-deep .character-sprites {
margin-bottom: -5px !important;
padding-bottom: 0 !important;
margin-top: -1px !important;
}
::v-deep .avatar {
margin-left: -1.75rem;
margin-right: -0.5rem;
}
}
.avatar-left {
margin-right: 1.5rem;
}
.avatar-right {
margin-left: -1rem;
overflow: clip;
margin-left: 1.5rem;
::v-deep .character-sprites {
margin-right: 1rem !important;
@@ -91,10 +107,19 @@
margin-bottom: 1rem;
padding: 0rem;
width: 684px;
}
.message-list {
width: 100%;
padding-right: 10px;
margin-right: 0 !important;
}
.message-row {
margin-left: 12px;
margin-right: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
@@ -102,26 +127,6 @@
}
}
}
@media only screen and (max-width: 1200px) {
.card {
width: 100%;
}
}
@media only screen and (min-width: 1400px) {
.message-row {
margin-left: -15px;
margin-right: -30px;
}
}
.card-left {
border: 1px solid $purple-500;
}
.card-right {
border: 1px solid $gray-500;
}
.hr {
width: 100%;
@@ -280,6 +285,9 @@ export default {
// container.style.overflowY = 'scroll';
}
}, 50),
messageLiked (message) {
this.$emit('message-liked', message);
},
messageRemoved (message) {
this.$emit('message-removed', message);
},

View File

@@ -427,7 +427,7 @@ export default {
this.$store.dispatch('user:fetch'),
this.$store.dispatch('tasks:fetchUserTasks'),
]).then(() => {
// NOTE: This is a timeout to ensure dom is loaded
// @TODO: This is a timeout to ensure dom is loaded
window.setTimeout(() => {
this.runForcedModals();
}, 2000);

View File

@@ -31,6 +31,13 @@
&colon;&nbsp;
<a href="mailto:admin@habitica.com">admin&commat;habitica&period;com</a>
<br>
{{ $t('generalQuestionsSite') }}
&colon;&nbsp;
<a
target="_blank"
@click.prevent="openBugReportModal(true)"
> {{ $t('askQuestion') }}</a>
<br>
{{ $t('businessInquiries') }}
&colon;&nbsp;
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
@@ -47,8 +54,10 @@
<script>
import { mapState } from '@/libs/store';
import { goToModForm } from '@/libs/modform';
import reportBug from '@/mixins/reportBug.js';
export default {
mixins: [reportBug],
computed: {
...mapState({
user: 'user.data',

View File

@@ -66,13 +66,16 @@
class="nav-link"
>{{ $t('presskit') }}</a>
</router-link>
<li class="nav-item">
<router-link
class="nav-item"
tag="li"
to="/static/contact"
>
<a
v-once
class="nav-link"
href="mailto:admin@habitica.com"
>{{ $t('contactUs') }}</a>
</li>
</router-link>
</ul>
<ul
v-else

View File

@@ -135,7 +135,7 @@
}
}
.bluesky svg {
.twitter svg {
background-color: $purple-50;
fill: $purple-500;
&:hover {

View File

@@ -20,6 +20,7 @@
}"
>
<input
ref="textInput"
:value="value"
class="form-control"
:type="inputType"
@@ -29,12 +30,15 @@
}"
:readonly="readonly"
:aria-readonly="readonly"
autocomplete="off"
:placeholder="placeholder"
@keyup="handleChange"
@keyup.enter="$emit('enter')"
@blur="$emit('blur')"
>
</div>
<template v-if="!hideErrorLine">
<div
v-for="issue in invalidIssues"
:key="issue"
@@ -42,6 +46,7 @@
>
{{ issue }} &nbsp;
</div>
</template>
</div>
</div>
</template>
@@ -85,6 +90,10 @@ export default {
type: Array,
default: () => [],
},
hideErrorLine: {
type: Boolean,
default: false,
},
},
data () {
return {
@@ -107,6 +116,9 @@ export default {
this.wasChanged = true;
this.$emit('update:value', value);
},
focus () {
this.$refs.textInput.focus();
},
},
};
</script>
@@ -128,4 +140,12 @@ export default {
margin-bottom: 0;
}
/* this removes safari "save username" UI, we only search for one, we dont want to save it */
input::-webkit-contacts-auto-fill-button,
input::-webkit-credentials-auto-fill-button {
visibility: hidden;
position: absolute;
right: 0;
}
</style>

View File

@@ -29,20 +29,12 @@
@import '~@/assets/scss/colors.scss';
.user-link { // this is the user name
font-family: 'Roboto Condensed', sans-serif;
font-weight: bold;
margin-bottom: 0;
cursor: pointer;
display: inline-block;
font-size: 16px;
// currently used in the member-details-new.vue
&.smaller {
font-family: Roboto;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
}
display: inline-flex !important;
&.no-tier {
color: $gray-50;
@@ -111,7 +103,6 @@ export default {
'backer',
'contributor',
'hideTooltip',
'smallerStyle',
'showBuffed',
'context',
],
@@ -173,7 +164,7 @@ export default {
return this.hideTooltip ? '' : achievementsLib.getContribText(this.contributor, this.isNPC) || '';
},
levelStyle () {
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)} ${this.smallerStyle ? 'smaller' : ''}`;
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)}`;
},
},
};

View File

@@ -0,0 +1,102 @@
import debounce from 'lodash/debounce';
export const autoCompleteHelperMixin = {
data () {
return {
mixinData: {
autoComplete: {
caretPosition: 0,
coords: {
TOP: 0,
LEFT: 0,
},
},
},
};
},
methods: {
autoCompleteMixinHandleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
autoCompleteMixinHandleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
autoCompleteMixinSelectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
autoCompleteMixinSelectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
autoCompleteMixinSelectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
autoCompleteMixinUpdateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
autoCompleteMixinResetCoordsPosition () {
this.mixinData.autoComplete.coords = {
TOP: 0,
LEFT: 0,
};
},
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
_getCoord (e, text) {
const caretPosition = text.selectionEnd;
this.mixinData.autoComplete.caretPosition = caretPosition;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, caretPosition);
span.textContent = text.value.substr(caretPosition) || '.';
div.appendChild(span);
this.mixinData.autoComplete.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this._getCoord(eventUpdate, text);
},
},
};

View File

@@ -1,7 +1,7 @@
import notifications from './notifications';
import { NotificationMixins } from './notifications';
export default {
mixins: [notifications],
export const CopyToClipboardMixin = {
mixins: [NotificationMixins],
methods: {
async mixinCopyToClipboard (valueToCopy, notificationToShow = null) {
if (navigator.clipboard) {
@@ -21,3 +21,5 @@ export default {
},
},
};
export default CopyToClipboardMixin;

View File

@@ -15,12 +15,26 @@
>
{{ $t('messages') }}
</h2>
<div class="placeholder svg-icon">
<!-- placeholder -->
</div>
</div>
<button
class="btn btn-secondary plus-button"
:class="{'new-message-mode':showStartNewConversationInput}"
@click="triggerStartNewConversationState()"
>
<div
v-if="selectedConversation && selectedConversation.key"
class="svg-icon icon-10 color"
v-html="icons.positive"
></div>
</button>
</div>
<start-new-conversation-input-header
v-if="showStartNewConversationInput"
@startNewConversation="startConversationByUsername($event)"
@cancelNewConversation="showStartNewConversationInput = false"
/>
<div
v-else-if="selectedConversation && selectedConversation.key"
class="d-flex selected-conversion"
>
<router-link
@@ -52,25 +66,11 @@
@change="toggleOpt()"
/>
</div>
<div
v-if="filtersConversations.length > 0"
class="conversations"
>
<conversation-item
v-for="conversation in filtersConversations"
:key="conversation.key"
:active-key="selectedConversation.key"
:contributor="conversation.contributor"
:backer="conversation.backer"
:uuid="conversation.key"
:display-name="conversation.name"
:username="conversation.username"
:last-message-date="conversation.date"
:last-message-text="conversation.lastMessageText
? removeTags(parseMarkdown(conversation.lastMessageText)) : ''"
@click="selectConversation(conversation.key)"
<pm-conversations-list
:filters-conversations="filtersConversations"
:selected-conversation="selectedConversation"
@selectConversation="selectConversation($event)"
/>
</div>
<button
v-if="canLoadMoreConversations"
class="btn btn-secondary"
@@ -79,28 +79,35 @@
{{ $t('loadMore') }}
</button>
</div>
<div class="messages-column d-flex flex-column align-items-center">
<div
v-if="filtersConversations.length === 0
&& (!selectedConversation || !selectedConversation.key)"
class="empty-messages m-auto text-center empty-sidebar"
v-if="user.inbox.optOut"
class="disable-background-in-message-list"
>
<div class="no-messages-box">
<div
<span
v-once
class="svg-icon envelope"
v-html="icons.messageIcon"
></div>
<h2 v-once>
{{ $t('emptyMessagesLine1') }}
</h2>
<p v-if="!user.flags.chatRevoked">
{{ $t('emptyMessagesLine2') }}
</p>
</div>
class="caption"
> {{ $t('PMDisabledCaptionTitle') }}. </span> &nbsp;
<span
v-once
class="text"
> {{ $t('PMDisabledCaptionText') }} </span>
</div>
<pm-empty-state
v-if="uiState === UI_STATES.NO_CONVERSATIONS"
:chat-revoked="user.flags.chatRevoked"
@newMessageClicked="showStartNewConversationInput = true"
/>
<pm-new-message-started
v-if="uiState === UI_STATES.START_NEW_CONVERSATION && selectedConversation.userStyles"
:member-obj="selectedConversation.userStyles"
/>
<div
v-if="filtersConversations.length !== 0 && !selectedConversation.key"
v-if="uiState === UI_STATES.NO_CONVERSATIONS_SELECTED"
class="empty-messages full-height m-auto text-center"
>
<div class="no-messages-box">
@@ -113,20 +120,7 @@
<p v-html="placeholderTexts.description"></p>
</div>
</div>
<div
v-if="selectedConversation.key && selectedConversationMessages.length === 0"
class="empty-messages full-height mt-auto text-center"
>
<avatar
v-if="selectedConversation.userStyles"
:member="selectedConversation.userStyles"
:avatar-only="true"
sprites-margin="0 0 0 -45px"
class="center-avatar"
/>
<h3>{{ $t('beginningOfConversation', {userName: selectedConversation.name}) }}</h3>
<p>{{ $t('beginningOfConversationReminder') }}</p>
</div>
<messageList
v-if="selectedConversation && selectedConversationMessages.length > 0"
ref="chatscroll"
@@ -136,16 +130,18 @@
:can-load-more="canLoadMore"
:is-loading="messagesLoading"
@message-removed="messageRemoved"
@message-liked="messageLiked"
@triggerLoad="infiniteScrollTrigger"
/>
<pm-disabled-state
v-if="disabledTexts?.showBottomInfo"
:disabled-texts="disabledTexts"
/>
<div
v-if="disabledTexts"
class="pm-disabled-caption text-center"
v-if="shouldShowInputPanel"
class="full-width"
>
<h4>{{ disabledTexts.title }}</h4>
<p>{{ disabledTexts.description }}</p>
</div>
<div class="full-width">
<div
class="new-message-row d-flex align-items-center"
>
@@ -174,7 +170,7 @@
:class="{'disabled':newMessageDisabled || newMessage === ''}"
@click="sendPrivateMessage()"
>
{{ $t('send') }}
{{ $t('sendMessage') }}
</button>
</div>
</div>
@@ -184,8 +180,8 @@
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/variables.scss';
@import '~@/assets/scss/colors';
@import '~@/assets/scss/variables';
$pmHeaderHeight: 56px;
@@ -254,13 +250,44 @@
letter-spacing: normal;
color: $gray-50;
}
.empty-messages {
flex-flow: column;
justify-content: center;
h3, p {
color: $gray-200;
margin: 0rem;
}
h2 {
color: $gray-200;
margin-bottom: 1rem;
}
.no-messages-box {
display: flex;
flex-direction: column;
align-items: center;
width: 330px;
}
.envelope {
color: $gray-400 !important;
svg {
width: 86px;
height: 64px;
}
}
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
@import '~@/assets/scss/variables.scss';
@import '~@/assets/scss/colors';
@import '~@/assets/scss/tiers';
@import '~@/assets/scss/variables';
$pmHeaderHeight: 56px;
$background: $white;
@@ -268,10 +295,15 @@
.header-bar {
height: 56px;
background-color: $white;
padding-left: 1.5rem;
padding-right: 1.5rem;
align-items: center;
.left-header {
padding-left: 1.5rem;
max-width: 330px;
align-items: center;
flex: 1;
}
.mail-icon {
width: 32px;
height: 24px;
@@ -285,6 +317,14 @@
.placeholder.svg-icon {
width: 32px;
}
.plus-button {
padding: 10px 14px;
&.new-message-mode {
color: $gray-200;
}
}
}
.full-height {
@@ -316,42 +356,24 @@
border-bottom: 1px solid $gray-500;
}
.conversations {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
.empty-messages {
h3, p {
color: $gray-200;
margin: 0rem;
}
h2 {
color: $gray-200;
margin-bottom: 1rem;
}
p {
font-size: 12px;
}
.no-messages-box {
.disable-background-in-message-list {
display: flex;
flex-direction: column;
align-items: center;
width: 330px;
justify-content: center;
height: 44px;
color: $yellow-1;
background: $yellow-500;
width: 100%;
.caption {
font-weight: 700;
line-height: 24px;
}
.envelope {
color: $gray-400 !important;
margin-bottom: 1.5rem;
::v-deep svg {
width: 64px;
height: 48px;
}
.text {
font-weight: 400;
line-height: 24px;
}
}
@@ -446,6 +468,7 @@
padding: 1.5rem;
.guidelines {
height: 32px;
font-size: 12px;
font-weight: normal;
font-style: normal;
@@ -458,10 +481,10 @@
}
button {
height: 32px;
border-radius: 4px;
line-height: 1.714;
margin-left: 1.5rem;
padding: 2px 12px;
white-space: nowrap;
&.disabled {
cursor: default;
@@ -473,30 +496,6 @@
}
}
.pm-disabled-caption {
padding-top: 1em;
z-index: 2;
h4, p {
color: $gray-200;
}
h4 {
margin-top: 0;
margin-bottom: 0.4em;
}
p {
font-size: 12px;
margin-bottom: 0;
}
}
.left-header {
max-width: calc(330px - 2rem); // minus the left padding
flex: 1;
}
.sidebar {
width: 330px;
background-color: $gray-700;
@@ -540,7 +539,7 @@
z-index: 1;
pointer-events: none;
box-shadow: 0 3px 12px 0 rgba($black, 0.24);
box-shadow: 0 3px 12px 0 rgba(26, 24, 29, 0.24);
}
.center-avatar {
@@ -549,36 +548,52 @@
</style>
<script>
import Vue from 'vue';
import Vue, { defineComponent } from 'vue';
import moment from 'moment';
import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import habiticaMarkdown from 'habitica-markdown';
import axios from 'axios';
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
import findIndex from 'lodash/findIndex';
import { mapState } from '@/libs/store';
import styleHelper from '@/mixins/styleHelper';
import toggleSwitch from '@/components/ui/toggleSwitch';
import userLink from '@/components/userLink';
import toggleSwitch from '@/components/ui/toggleSwitch.vue';
import userLink from '@/components/userLink.vue';
import messageList from '@/components/messages/messageList';
import messageList from '@/components/messages/messageList.vue';
import messageIcon from '@/assets/svg/message.svg';
import mail from '@/assets/svg/mail.svg';
import conversationItem from '@/components/messages/conversationItem';
import faceAvatar from '@/components/faceAvatar';
import Avatar from '@/components/avatar';
import faceAvatar from '@/components/faceAvatar.vue';
import { EVENTS } from '@/libs/events';
import PmConversationsList from './pm-conversations-list.vue';
import PmEmptyState from './pm-empty-state.vue';
import PmDisabledState from './pm-disabled-state.vue';
import PmNewMessageStarted from './pm-new-message-started.vue';
import StartNewConversationInputHeader from './start-new-conversation-input-header.vue';
import positiveIcon from '@/assets/svg/positive.svg';
import NotificationMixins from '@/mixins/notifications';
// extract to a shared path
const CONVERSATIONS_PER_PAGE = 10;
const PM_PER_PAGE = 10;
export default {
const UI_STATES = Object.freeze({
LOADING: 'LOADING',
NO_CONVERSATIONS: 'NO_CONVERSATIONS',
NO_CONVERSATIONS_SELECTED: 'NO_CONVERSATIONS_SELECTED',
START_NEW_CONVERSATION: 'START_NEW_CONVERSATION',
CONVERSATION_SELECTED: 'CONVERSATION_SELECTED',
});
export default defineComponent({
components: {
Avatar,
StartNewConversationInputHeader,
PmNewMessageStarted,
PmDisabledState,
PmEmptyState,
PmConversationsList,
messageList,
toggleSwitch,
conversationItem,
userLink,
faceAvatar,
},
@@ -587,7 +602,7 @@ export default {
return moment(new Date(value)).fromNow();
},
},
mixins: [styleHelper],
mixins: [styleHelper, NotificationMixins],
beforeRouteEnter (to, from, next) {
next(vm => {
const data = vm.$store.state.privateMessageOptions;
@@ -610,17 +625,26 @@ export default {
icons: Object.freeze({
messageIcon,
mail,
positive: positiveIcon,
}),
loaded: false,
UI_STATES,
showStartNewConversationInput: false,
newConversationTargetUser: null,
loadingConversations: true,
showPopover: false,
/* Conversation-specific data */
/**
* @type {PrivateMessages.InitiatedConversation}
*/
initiatedConversation: null,
updateConversationsCounter: 0,
selectedConversation: {},
conversationPage: 0,
canLoadMoreConversations: false,
/** @type {PrivateMessages.ConversationSummaryMessageEntry[]} */
loadedConversations: [],
/** @type {Record<string, PrivateMessages.PrivateMessageEntry[]>} */
messagesByConversation: {}, // cache {uuid: []}
newMessage: '',
@@ -653,9 +677,15 @@ export default {
}];
}
// Create conversation objects
/** @type {PrivateMessages.ConversationEntry[]} */
const convos = [];
for (const key in inboxGroup) {
if (Object.prototype.hasOwnProperty.call(inboxGroup, key)) {
/**
* @type {PrivateMessages.ConversationSummaryMessageEntry}
*/
const recentMessage = inboxGroup[key][0];
const convoModel = {
@@ -709,9 +739,6 @@ export default {
return ordered;
},
currentLength () {
return this.newMessage.length;
},
placeholderTexts () {
if (this.user.flags.chatRevoked) {
return {
@@ -724,24 +751,22 @@ export default {
description: this.$t('PMPlaceholderDescription'),
};
},
disabledTexts () {
if (this.user.flags.chatRevoked) {
return {
enableInput: false,
showBottomInfo: true,
title: this.$t('PMPlaceholderTitleRevoked'),
description: this.$t('chatPrivilegesRevoked'),
};
}
if (this.user.inbox.optOut) {
return {
title: this.$t('PMDisabledCaptionTitle'),
description: this.$t('PMDisabledCaptionText'),
};
}
if (this.selectedConversation?.key) {
if (this.user.inbox.blocks.includes(this.selectedConversation.key)) {
return {
enableInput: false,
showBottomInfo: true,
title: this.$t('PMDisabledCaptionTitle'),
description: this.$t('PMUnblockUserToSendMessages'),
};
@@ -749,12 +774,23 @@ export default {
if (!this.selectedConversation.canReceive) {
return {
enableInput: false,
showBottomInfo: true,
title: this.$t('PMCanNotReply'),
description: this.$t('PMUserDoesNotReceiveMessages'),
};
}
}
if (this.user.inbox.optOut) {
return {
enableInput: true,
showBottomInfo: false,
title: this.$t('PMDisabledCaptionTitle'),
description: this.$t('PMDisabledCaptionText'),
};
}
return null;
},
optTextSet () {
@@ -776,8 +812,51 @@ export default {
return '';
},
newMessageDisabled () {
return !this.selectedConversation || !this.selectedConversation.key
|| this.disabledTexts !== null;
if (this.disabledTexts) {
return !this.disabledTexts.enableInput;
}
return [
UI_STATES.NO_CONVERSATIONS_SELECTED,
UI_STATES.NO_CONVERSATIONS,
UI_STATES.LOADING,
].includes(this.uiState);
},
uiState () {
if (this.loadingConversations) {
return UI_STATES.LOADING;
}
if (this.loadedConversations.length === 0) {
return UI_STATES.NO_CONVERSATIONS;
}
// Hiding the "Select a conversation on the left" state,
// and just picking the first conversation once it loads, right away
// see reload method
/* if (!this.selectedConversation.key) {
return UI_STATES.NO_CONVERSATIONS_SELECTED;
} */
if (this.selectedConversationMessages.length === 0) {
return UI_STATES.START_NEW_CONVERSATION;
}
return UI_STATES.CONVERSATION_SELECTED;
},
shouldShowInputPanel () {
const currentUiState = this.uiState;
switch (currentUiState) {
case UI_STATES.CONVERSATION_SELECTED:
case UI_STATES.START_NEW_CONVERSATION: {
return true;
}
default: {
return false;
}
}
},
},
async mounted () {
@@ -787,15 +866,11 @@ export default {
// notification click to refresh
this.$root.$on(EVENTS.PM_REFRESH, async () => {
await this.reload();
this.selectFirstConversation();
});
// header sync button
this.$root.$on(EVENTS.RESYNC_COMPLETED, async () => {
await this.reload();
this.selectFirstConversation();
});
await this.reload();
@@ -828,7 +903,7 @@ export default {
methods: {
async reload () {
this.loaded = false;
this.loadingConversations = true;
this.conversationPage = 0;
this.loadedConversations = [];
@@ -838,11 +913,15 @@ export default {
await this.$store.dispatch('user:markPrivMessagesRead');
this.loaded = true;
await this.selectFirstConversation();
this.loadingConversations = false;
},
async loadConversations () {
const query = ['/api/v4/inbox/conversations'];
query.push(`?page=${this.conversationPage}`);
const query = [
'/api/v4/inbox/conversations',
`?page=${this.conversationPage}`,
];
this.conversationPage += 1;
const conversationRes = await axios.get(query.join(''));
@@ -850,6 +929,12 @@ export default {
this.canLoadMoreConversations = loadedConversations.length === CONVERSATIONS_PER_PAGE;
this.loadedConversations.push(...loadedConversations);
},
messageLiked (message) {
const messages = this.messagesByConversation[this.selectedConversation.key];
const chatIndex = findIndex(messages, chatMessage => chatMessage.id === message.id);
messages.splice(chatIndex, 1, message);
},
messageRemoved (message) {
const messages = this.messagesByConversation[this.selectedConversation.key];
@@ -916,38 +1001,43 @@ export default {
this.selectedConversation.lastMessageText = this.newMessage;
this.selectedConversation.date = new Date();
this.scrollToBottom();
this.$store.dispatch('members:sendPrivateMessage', {
toUserId: this.selectedConversation.key,
message: this.newMessage,
}).then(response => {
const newMessage = response.data.data.message;
const messageToReset = messages[messages.length - 1];
messageToReset.id = newMessage.id; // just set the id, all other infos already set
messageToReset.text = newMessage.text; // handle mentions
// just set the id, all other infos already set
messageToReset.id = newMessage.id;
messageToReset.text = newMessage.text;
messageToReset.uniqueMessageId = newMessage.uniqueMessageId;
Object.assign(messages[messages.length - 1], messageToReset);
this.updateConversationsCounter += 1;
});
this.newMessage = '';
setTimeout(() => {
this.scrollToBottom();
}, 150);
},
scrollToBottom () {
if (!this.$refs.chatscroll) {
return;
}
const chatscrollBeforeTick = this.$refs.chatscroll.$el;
chatscrollBeforeTick.scrollTop = chatscrollBeforeTick.scrollHeight;
Vue.nextTick(() => {
if (!this.$refs.chatscroll) return;
if (!this.$refs.chatscroll) {
return;
}
const chatscroll = this.$refs.chatscroll.$el;
chatscroll.scrollTop = chatscroll.scrollHeight;
});
},
removeTags (html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
},
parseMarkdown (text) {
if (!text) return null;
return habiticaMarkdown.render(String(text));
},
infiniteScrollTrigger () {
// show loading and wait until the loadMore debounced
// or else it would trigger on every scrolling-pixel (while not loading)
@@ -983,11 +1073,81 @@ export default {
this.selectedConversation.canLoadMore = loadedMessages.length === PM_PER_PAGE;
this.messagesLoading = false;
},
selectFirstConversation () {
async selectFirstConversation () {
if (this.loadedConversations.length > 0) {
this.selectConversation(this.loadedConversations[0].uuid, true);
await this.selectConversation(this.loadedConversations[0].uuid, true);
}
},
triggerStartNewConversationState () {
this.showStartNewConversationInput = true;
},
async startConversationByUsername (targetUserName) {
// check if the target user exists in current conversations, select that conversation
/** @type {PrivateMessages.ConversationSummaryMessageEntry} */
const foundConversation = this.loadedConversations.find(c => c.username === targetUserName);
if (foundConversation) {
this.selectConversation(foundConversation.uuid);
this.showStartNewConversationInput = false;
return;
}
let loadedMember = null;
try {
loadedMember = await this.$store.dispatch('members:fetchMemberByUsername', {
username: targetUserName,
});
} catch {
loadedMember = null;
}
if (!loadedMember) {
this.error(this.$t('targetUserNotExist', { userName: targetUserName }));
return;
}
const loadedMemberUUID = loadedMember.id;
this.showStartNewConversationInput = false;
// otherwise create a dummy conversation, load messages for that user
/**
* @type {PrivateMessages.ConversationSummaryMessageEntry}
*/
const newConversationItem = {
uuid: loadedMemberUUID,
user: loadedMember.profile.name,
username: loadedMember.auth.local.username,
contributor: loadedMember.contributor,
userStyles: loadedMember,
canReceive: loadedMember.inbox.canReceive,
timestamp: new Date(),
count: 0,
text: '',
};
this.loadedConversations.splice(0, 0, newConversationItem);
this.selectConversation(loadedMemberUUID);
if (this.messagesByConversation[loadedMemberUUID]) {
const messageLengthByConversation = this.messagesByConversation[loadedMemberUUID].length;
// if messages already exists, update the sidebar entry last message
if (messageLengthByConversation > 0) {
/** @type {PrivateMessages.PrivateMessageEntry} */
const lastMessage = this.messagesByConversation[loadedMemberUUID][messageLengthByConversation - 1];
newConversationItem.lastMessageText = lastMessage.text;
return;
}
}
this.newConversationTargetUser = loadedMember;
},
},
});
</script>

View File

@@ -62,7 +62,7 @@
<script>
import moment from 'moment';
import userLabel from '../userLabel';
import userLabel from '../../components/userLabel.vue';
import dots from '@/assets/svg/dots.svg';
import block from '@/assets/svg/block.svg';
@@ -117,7 +117,7 @@ export default {
</script>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors';
.action-padding {
height: 24px !important;
@@ -153,7 +153,7 @@ export default {
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors';
.conversation {
padding: 1rem 1.5rem;

View File

@@ -0,0 +1,64 @@
<template>
<div
v-if="filtersConversations.length > 0"
class="conversations"
>
<conversation-item
v-for="conversation in filtersConversations"
:key="conversation.key"
:active-key="selectedConversation?.key"
:contributor="conversation.contributor"
:backer="conversation.backer"
:uuid="conversation.key"
:display-name="conversation.name"
:username="conversation.username"
:last-message-date="conversation.date"
:last-message-text="conversation.lastMessageText
? removeTags(parseMarkdown(conversation.lastMessageText)) : ''"
@click="selectConversation(conversation.key)"
/>
</div>
</template>
<style scoped lang="scss">
.conversations {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
</style>
<script>
import { defineComponent } from 'vue';
import habiticaMarkdown from 'habitica-markdown';
import conversationItem from '@/pages/private-messages/pm-conversation-item.vue';
export default defineComponent({
components: { conversationItem },
props: {
filtersConversations: {
type: Array,
default: () => [],
},
selectedConversation: {
type: Object,
default: null,
},
},
methods: {
removeTags (html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
},
parseMarkdown (text) {
if (!text) return null;
return habiticaMarkdown.render(String(text));
},
selectConversation (conversationKey) {
this.$emit('selectConversation', conversationKey);
},
},
});
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div
class="pm-disabled-caption text-center"
>
<h4>{{ disabledTexts.title }}</h4>
<p>{{ disabledTexts.description }}</p>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.pm-disabled-caption {
padding-top: 1.5em;
z-index: 2;
h4, p {
color: $gray-200;
}
h4 {
margin-top: 0;
margin-bottom: 0.4em;
}
p {
font-size: 12px;
margin-bottom: 0;
}
}
</style>
<script>
export default {
props: ['disabledTexts'],
};
</script>

View File

@@ -0,0 +1,71 @@
<template>
<div
class="empty-messages m-auto text-center empty-sidebar"
>
<div class="no-messages-box">
<div
v-once
class="svg-icon envelope mb-4"
v-html="icons.mailIcon"
></div>
<strong
v-once
class="mb-1"
>
{{ $t('emptyMessagesLine1') }}
</strong>
<p v-if="!chatRevoked">
{{ $t('emptyMessagesLine2') }}
</p>
</div>
<button
class="btn btn-primary mt-4 d-flex align-items-center"
@click="$emit('newMessageClicked')"
>
<div
class="svg-icon icon-10 color mr-2"
v-html="icons.positive"
></div>
{{ $t('newMessage') }}
</button>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
strong {
line-height: 1.71;
color: $gray-100;
}
.svg-icon.icon-10 {
margin: 3px;
}
p {
font-size: 14px;
font-weight: 400;
line-height: 24px;
}
</style>
<script>
import mailIcon from '@/assets/svg/mail.svg';
import positiveIcon from '@/assets/svg/positive.svg';
export default {
props: {
chatRevoked: Boolean,
},
data () {
return {
icons: Object.freeze({
mailIcon,
positive: positiveIcon,
}),
};
},
};
</script>

View File

@@ -0,0 +1,69 @@
<template>
<div
v-once
class="centered empty-messages m-auto text-center"
>
<avatar
v-if="memberObj"
:member="memberObj"
:avatar-only="true"
:show-weapon="false"
:hide-class-badge="true"
:override-top-padding="'0px'"
:sprites-margin="'0 0 0 -30px'"
:debug-mode="false"
:center-avatar="true"
class="mb-3"
/>
<strong>{{ memberObj.profile.name }}</strong>
<div class="username mb-3">
@{{ memberObj.auth.local.username }}
</div>
<div
class="kind-text"
v-html="$t('rememberToBeKind')"
></div>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.centered {
align-content: center;
}
.center-avatar {
margin: 0 auto;
}
strong {
line-height: 1.71;
}
.username {
font-size: 12px;
line-height: 1.33;
color: $gray-100;
margin-top: -4px;
}
.kind-text {
width: 330px;
line-height: 1.71;
color: $gray-100;
}
</style>
<script>
import Avatar from '@/components/avatar.vue';
export default {
components: { Avatar },
props: {
memberObj: null,
},
};
</script>

View File

@@ -0,0 +1,44 @@
export namespace PrivateMessages {
// Shared properties between message types
interface SharedMessageProps {
username: string;
contributor: Record<string, unknown>;
userStyles: Record<string, unknown>;
canReceive: boolean;
}
/**
* This is the Type we get from our API
*/
interface ConversationSummaryMessageEntry extends SharedMessageProps {
uuid: string;
user: string;
timestamp: string;
text: string;
count: number;
}
/**
* The Visual (Sidebar) Entry
*/
interface ConversationEntry extends SharedMessageProps {
/**
* UUID
*/
key: string;
name: string;
lastMessageText: '',
canLoadMore: boolean;
page: 0
}
/**
* Loaded Private Messages, partial type
*/
interface PrivateMessageEntry extends SharedMessageProps {
text: string;
}
}

View File

@@ -0,0 +1,167 @@
<template>
<div class="ml-4">
<strong
v-once
v-html="$t('to')"
></strong>
<validated-text-input
id="selectUser"
ref="targetUserInput"
v-model="targetUserInputValue"
class="mx-2"
:is-valid="foundUser._id"
:only-show-invalid-state="foundUser._id === undefined"
:hide-error-line="true"
:placeholder="$t('usernameOrUserId')"
:invalid-issues="userInputInvalidIssues"
@enter="triggerNewConversation"
/>
<button
class="btn btn-primary"
:disabled="preventTrigger"
@click="triggerNewConversation()"
>
{{ $t('confirm') }}
</button>
<button
class="ml-2 btn btn-secondary"
@click="$emit('cancelNewConversation')"
>
{{ $t('cancel') }}
</button>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
div {
display: flex;
align-items: center;
}
div > * {
height: 32px;
}
strong {
line-height: 1.71;
align-content: center;
}
input {
border-radius: 2px;
border-width: 2px;
width: 420px;
}
#selectUser {
/* changing the style of validate-text-input to the same as others */
::v-deep {
.input-group {
border-width: 2px;
input {
width: 420px;
height: 100%;
color: $gray-50;
}
}
.input-group {
&:focus, &:active, &:focus-within {
border: solid 2px $purple-400;
}
}
}
}
</style>
<script>
import debounce from 'lodash/debounce';
import isUUID from 'validator/es/lib/isUUID';
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
export default {
components: {
ValidatedTextInput,
},
mixins: [],
data () {
return {
targetUserInputValue: '',
userNotFound: false,
foundUser: {},
};
},
computed: {
preventTrigger () {
return this.targetUserInputValue.length < 2;
},
userInputInvalidIssues () {
return this.targetUserInputValue.length > 0 && this.userNotFound
? [this.$t('userWithUsernameOrUserIdNotFound')]
: [''];
},
},
watch: {
targetUserInputValue: {
handler () {
this.searchUser(this.targetUserInputValue.replace('@', ''));
},
},
},
mounted () {
this.$refs.targetUserInput.focus();
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'select-user-modal');
},
searchUser: debounce(async function userSearch (searchTerm = '') {
this.foundUser = {};
if (searchTerm.length < 1) {
this.userNotFound = false;
return;
}
let result;
if (isUUID(searchTerm)) {
try {
result = await this.$store.dispatch('members:fetchMember', {
memberId: searchTerm,
});
} catch {
result = null;
}
} else {
try {
result = await this.$store.dispatch('members:fetchMemberByUsername', {
username: searchTerm,
});
} catch {
result = null;
}
}
if (!result) {
this.userNotFound = true;
return;
}
this.userNotFound = false;
this.foundUser = result;
}, 500),
triggerNewConversation () {
const userWithoutAt = this.$refs.targetUserInput.value.replace('@', '');
this.$emit('startNewConversation', userWithoutAt);
},
},
};
</script>

View File

@@ -49,7 +49,7 @@ const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ '@/com
const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/taskInformation');
const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/billing');
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages');
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages/index.vue');
// Challenges
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/index');
@@ -219,7 +219,7 @@ const router = new VueRouter({
// Only used to handle some redirects
// See router.beforeEach
{ path: '/static/tavern-and-guilds', redirect: '/static/faq/tavern-and-guilds' },
{ path: '/static/faq/tavern-and-guilds', redirect: '/static/tavern-and-guilds' },
{ path: '/redirect/:redirect', name: 'redirect' },
{ path: '*', redirect: { name: 'notFound' } },
],

View File

@@ -43,7 +43,14 @@ export async function deleteChat (store, payload) {
}
export async function like (store, payload) {
const url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatId}/like`;
let url = '';
if (payload.groupId === 'privateMessage') {
url = `/api/v4/inbox/like-private-message/${payload.chatMessageId}`;
} else {
url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatMessageId}/like`;
}
const response = await axios.post(url);
return response.data.data;
}

View File

@@ -1,14 +1,16 @@
import Vue from 'vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import ChatCard from '@/components/chat/chatCard.vue';
import BootstrapVue from 'bootstrap-vue';
import MessageCard from '@/components/messages/messageCard.vue';
import Store from '@/libs/store';
const localVue = createLocalVue();
localVue.use(Store);
localVue.use(Vue.directive('b-tooltip', {}));
localVue.use(BootstrapVue);
describe('ChatCard', () => {
describe('MessageCard', () => {
function createMessage (text) {
return { text, likes: {} };
}
@@ -26,7 +28,7 @@ describe('ChatCard', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(ChatCard, {
wrapper = shallowMount(MessageCard, {
propsData: { msg: message },
store: new Store({
state: {

View File

@@ -43,7 +43,6 @@ envVars
});
const webpackPlugins = [
new webpack.ProvidePlugin({ 'window.jQuery': 'jquery' }),
new webpack.DefinePlugin(envObject),
new MomentLocalesPlugin({
localesToKeep: ['bg',

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Моля, помогнете на модераторите, като ни кажете защо докладвате тази публикация за нарушение, например: защото е нежелана, включва ругатни, клетви, фанатизъм, обиди, теми за възрастни, насилие.",
"optional": "Незадължително",
"needsTextPlaceholder": "Въведете съобщението си тук.",
"copyMessageAsToDo": "Копиране на съобщението като задача",
"copyAsTodo": "Копиране като задача за изпълнение",
"messageAddedAsToDo": "Съобщението беше копирано като задача.",
"leaderOnlyChallenges": "Само водачът на групата може да създава предизвикателства",
"sendGift": "Изпращане на подарък",
"inviteFriends": "Поканете приятели",

View File

@@ -46,8 +46,10 @@
"messageNotAbleToBuyInBulk": "Не може да се закупи повече от един брой от този предмет.",
"notificationsRequired": "Идентификаторите на известията са задължителни.",
"unallocatedStatsPoints": "Имате <span class=\"notification-bold-blue\"><%= points %> неразпределени показателни точки</span>",
"beginningOfConversation": "Това е началото на разговора Ви с <%= userName %>.",
"messageDeletedUser": "Съжаляваме, но този потребител е изтрил профила си.",
"messageMissingDisplayName": "Липсва екранно име.",
"canDeleteNow": "Вече може да изтриете съобщението, ако желаете.",
"reportedMessage": "Вие докладвахте това съобщние на модераторите."
"reportedMessage": "Вие докладвахте това съобщние на модераторите.",
"beginningOfConversationReminder": "Не забравяйте да бъдете мили, уважителни и да следвате Обществените Правила!"
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Prosím pomož našim moderatorům a vysvětli, proč ohlašuješ tento příspěvek kvůli porušení pravidel, tedy zda je to spam, sprostá slova, náboženské přísahy, netolerance, urážky, témata nevhodná pro mladistvé, násilí.",
"optional": "Možný",
"needsTextPlaceholder": "Napiš svou zprávu sem.",
"copyMessageAsToDo": "Zkopírovat zprávu jako úkol",
"copyAsTodo": "Zkopírovat jako úkol",
"messageAddedAsToDo": "Zpráva zkopírována jako úkol.",
"leaderOnlyChallenges": "Pouze velitel družiny může vytvářet Výzvy",
"sendGift": "Poslat dárek",
"inviteFriends": "Pozvat přátele",

View File

@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Tento předmět nelze nakoupit v množství větším, než je 1.",
"notificationsRequired": "Id upozornění je potřeba.",
"unallocatedStatsPoints": "Máš <span class=\"notification-bold-blue\"><%= points %> nepřidělený(ch) vlastnostní(ch) bod(ů)</span>",
"beginningOfConversation": "Toto je začátek tvé konverzace s uživatelem <%= userName %>.",
"messageDeletedUser": "Omlouváme se, ale tento uživatel smazal svůj účet.",
"messageMissingDisplayName": "Chybí zobrazované jméno.",
"canDeleteNow": "Nyní můžete zprávu smazat.",
"reportedMessage": "Tuto zprávu jste nahlásili moderátorům.",
"beginningOfConversationReminder": "Nezapomeňte být milí, taktní a respektujte Zásady komunity!",
"messageAllUnEquipped": "Vše odloženo.",
"messageBackgroundUnEquipped": "Pozadí odloženo.",
"messagePetMountUnEquipped": "Mazlíček a zvíře odloženi.",

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Du kan hjælpe vores moderatorer ved at lade os vide, hvorfor du anmelder denne besked som en overtrædelse - fx spam, banden, religiøse kraftudtryk, fordomme, nedladende skældsord, emner for aldersgruppen +18 eller vold.",
"optional": "Valgfri",
"needsTextPlaceholder": "Skriv din besked her.",
"copyMessageAsToDo": "Kopier besked som To-Do",
"copyAsTodo": "Kopier som To-Do",
"messageAddedAsToDo": "Besked kopieret som To-Do.",
"leaderOnlyChallenges": "Kun gruppelederen kan oprette udfordringer",
"sendGift": "Send gave",
"inviteFriends": "Invitér venner",

View File

@@ -46,11 +46,13 @@
"messageNotAbleToBuyInBulk": "Denne genstand kan ikke købes i antal større end 1.",
"notificationsRequired": "Notafikation ID'er er krævet.",
"unallocatedStatsPoints": "Du har <span class=\"notification-bold-blue\"><%= points %> ufordelte Egenskabspoint</span>",
"beginningOfConversation": "Dette er begyndelsen på din samtale med <%= userName %>.",
"messageDeletedUser": "Sorry, this user has deleted their account.",
"messageMissingDisplayName": "Missing display name.",
"newsPostNotFound": "News Post er ikke fundet eller du har ikke adgang.",
"canDeleteNow": "Du kan nu slette beskeden, hvis du ønsker det.",
"reportedMessage": "Du har indrapporteret denne besked til moderatorerne.",
"beginningOfConversationReminder": "Husk at være venlig, respektful og følge Retningslinjerne for Fællesskabet!",
"messageAllUnEquipped": "Alt fjernet.",
"messageBackgroundUnEquipped": "Baggrund fjernet.",
"messageCostumeUnEquipped": "Kostume fjernet.",

View File

@@ -5,7 +5,7 @@
"webFaqStillNeedHelp": "Wenn Du eine Frage hast, die hier oder im [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ) nicht beantwortet wurde, verwende das Stell eine Frage Formular [LINK NEEDED]! Wir helfen Dir gerne.",
"parties": "Partys",
"webFaqAnswer25": "Habitica verwendet drei verschiedene Aufgabentypen, um deinen Bedürfnissen gerecht zu werden: Gewohnheiten, tägliche Aufgaben und To-Dos.\n\nGewohnheiten können positiv oder negativ sein und stellen etwas dar, das Sie vielleicht mehrmals am Tag oder nach einem nicht festgelegten Zeitplan verfolgen möchten. Positive Gewohnheiten bringen euch Belohnungen wie Gold und Erfahrung (Exp), während ihr bei negativen Gewohnheiten Lebenspunkte (HP) verliert.\n\nDailies sind wiederkehrende Aufgaben, die du nach einem strukturierten Zeitplan erledigen möchtest. Zum Beispiel einmal am Tag, dreimal in der Woche oder viermal im Monat. Wenn du Dailies verpasst, verlierst du HP, aber je schwieriger sie sind, desto besser ist die Belohnung!\n\nTo-Dos sind einmalige Aufgaben, für deren Erledigung es Belohnungen gibt. To-Dos können ein Fälligkeitsdatum haben, aber du verlierst keine HP, wenn du es verpasst.\n\nWähle die Aufgabenart, die am besten zu dem passt, was du erreichen willst!",
"commonQuestions": "Häufige Fragen",
"commonQuestions": "Häufige Fragenj",
"faqQuestion25": "Welche Aufgabentypen gibt es?",
"faqQuestion26": "Was sind einige Beispielaufgaben?",
"webFaqAnswer31": "Wenn du eine Aufgabe erfüllst und HP verlierst, obwohl du das nicht hättest tun sollen, kam es zu einer Verzögerung, während der Server die auf anderen Plattformen vorgenommenen Änderungen synchronisiert hat. Wenn du zum Beispiel Gold oder Mana verwendest oder HP in der mobilen App verlierst und dann eine Aufgabe auf der Website erledigst, bestätigt der Server lediglich, dass alles synchronisiert ist.",

View File

@@ -3189,69 +3189,5 @@
"shieldSpecialFall2024WarriorNotes": "Komplikationen bei Aufgaben werden von deinem Schild absorbiert und machen dich entschlossener. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"shieldSpecialFallRogue2024Notes": "Die Aufgaben selbst werden von den Wirbeln und Spiralen dieser hypnotischen Waffe überwältigt. Erhöht Stärke um <%= str %>. Limitierte Ausgabe Herbstausrüstung 2024.",
"headAccessoryMystery202212Notes": "Mit dieser verschnörkelten goldenen Tiara werden deine Herzlichkeit und Freundschaft neue Höhen erreichen. Gewährt keinen Attributbonus. Dezember 2022 Abonnentengegenstand.",
"headAccessoryMystery202212Text": "Eis-Tiara",
"shieldMystery202408Notes": "Die magischen Lichter beleuchten das Innere deines Seifenblasenverstecks oder jeden anderen Ort, an dem du ein wenig Licht brauchst! Gewährt keinen Attributbonus. August 2024 Abonnentengegenstand.",
"shieldArmoireJewelersPliersText": "Juwelierzange",
"shieldArmoireJewelersPliersNotes": "Sie schneidet, biegt, presst und vieles mehr. Mit diesem Werkzeug kannst du alles machen, was du dir vorstellen kannst. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Juwelierset (Gegenstand 3 von 4).",
"shieldArmoireTeaKettleNotes": "In diesem Kessel kannst du all deine geschmackvollen Lieblingstees aufbrühen. Hast du Lust auf schwarzen Tee, grünen Tee, Oolong oder vielleicht einen Kräutertee? Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Teekränzchen Set (Gegenstand 3 von 3).",
"shieldArmoireBasketballNotes": "Zisch! Wann immer du diesen magischen Basketball abschießt, wird es nichts als Treffer geben. Erhöht Ausdauer und Stärke um jeweils <%= attrs %> . Verzauberter Schrank: Altmodisches Basketballset (Gegenstand 2 von 2).",
"shieldArmoirePaintersPaletteNotes": "Farben in allen Facetten des Regenbogens stehen dir zur Verfügung. Ist es Magie, die sie so lebendig macht, wenn du sie benutzt, oder ist es dein Talent? Erhöht Stärke um <%= str %>. Verzauberter Schrank: Malerset (Gegenstand 4 von 4).",
"shieldArmoireBucketNotes": "Obwohl dieser Eimer für eine Mischung aus Wasser und Reinigungslösung gedacht ist, kannst du ihn auch zum Sammeln, Tragen und Transportieren von allem verwenden, was hineinpasst! Erhöht Stärke und Intelligenz um jeweils <%= attrs %>. Verzauberter Schrank: Reinigungs-Set 2 (Gegenstand 1 von 3)",
"backMystery202402Text": "Paradiesische Pinke Herzen",
"shieldArmoireSaucepanNotes": "Schau in diesen dampfenden Kochtopf und finde die Antwort auf das bestgehütete Geheimnis des Lebens! (Suppe. Die Antwort ist immer Suppe.) ErhöhtWahrnehmung um <%= per %>. Verzauberter Schrank: Küchenwerkzeugset 2 (Gegenstand 1 von 2).",
"shieldArmoireBuoyantBeachBallNotes": "Hast du schon zu viele Bälle in der Luft? Hier ist einer, den du sicher absetzen, rollen, hüpfen und hüpfen und hüpfen lassen kannst... Erhöht Stärke um <%= str %>. Verzauberter Schrank: Strand-Set (Gegenstand 4 von 4).",
"shieldArmoireTrustyPencilNotes": "Du weißt, was man sagt: Der Bleistift ist mächtiger als der Schwertstift. Moment... das klingt nicht ganz richtig... Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Schuluniformset (Gegenstand 4 von 4).",
"backMystery202401Notes": "Beschwöre sanftes Schneegestöber herauf oder rufe einen mächtigen Schneesturm herbei. Du hast die Wahl! Gewährt keinen Attributbonus. Jänner 2024 Abonnentengegenstand.",
"backMystery202402Notes": "Lass dich von einer Aura liebevoller Energie umgeben, wohin du auch gehst! Gewährt keinen Attributbonus. Februar 2024 Abonnentengegenstand.",
"backMystery202302Text": "Betrügerischer Tabby-Schweif",
"backMystery202301Notes": "Diese flauschigen Schweife enthalten ätherische Kräfte und auch ein hohes Maß an Charme! Gewährt keinen Attributbonus. Jänner 2023 Abonnentengegenstand.",
"backMystery202302Notes": "Wann immer du diesen Schweif trägst, wird es ein toller Tag werden! Callooh! Callay! Gewährt keinen Attributbonus. Februar 2023 Abonnentengegenstand.",
"backMystery202305Text": "Abendliche Flügel",
"backMystery202305Notes": "Fang das Funkeln des Abendsterns ein und schwebe auf diesen Flügeln in fremde Gefilde. Gewährt keinen Attributbonus. Mai 2023 Abonnentengegenstand.",
"backMystery202309Text": "Kolossale Kometenmottenflügel",
"backMystery202309Notes": "Flattere durch Wälder, gleite über Berge und schwebe über Ozeane auf diesen hellen und schönen Flügeln. Gewährt keinen Attributbonus. September 2023 Abonnentengegenstand.",
"backSpecialAnniversaryText": "Habitica Helden Cape",
"backSpecialAnniversaryNotes": "Lass dieses stolze Cape im Wind flattern und erzähle jedem, dass du ein Habitica Held bist. Gewährt keinen Attributbonus. Gegenstands-Sonderausgabe zur 10. Geburtstagsfeier.",
"backSpecialHeroicAureoleText": "Heroische Aureole",
"backSpecialHeroicAureoleNotes": "Die Edelsteine auf dieser Aureole schimmern, wenn du deine ruhmvollen Geschichten erzählst. Erhöht alle Eigenschaften um <%= attrs %>.",
"bodySpecialAnniversaryText": "Habiticas Heldenkragen",
"bodySpecialAnniversaryNotes": "Ergänzt dein königspurpurnes Kostüm perfekt! Gewährt keinen Attributbonus. Sonderausgaben-Gegenstand zur 10. Geburtstagsfeier.",
"eyewearMystery202312Text": "Winterliche blaue Augen",
"bodyArmoireKarateBrownBeltNotes": "Dieser Gürtel ist für diejenigen, deren Techniken und Fähigkeiten ausgereift sind. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Karateset (Gegenstand 9 von 10).",
"bodyArmoireKarateBrownBeltText": "Brauner Gürtel",
"headAccessoryMystery202302Text": "Trickbetrüger Tabby-Ohren",
"headAccessoryMystery202307Text": "Krakenkrone",
"bodyArmoireKarateOrangeBeltNotes": "Dieser Gürtel ist für diejenigen, die sich gesteigert und das Einsteigerlevel gemeistert haben. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Karateset (Gegenstand 4 von 10).",
"bodyArmoireKarateGreenBeltText": "Grüner Gürtel",
"bodyArmoireKarateBlackBeltText": "Schwarzer Gürtel",
"bodyArmoireKarateYellowBeltNotes": "Dieser Gürtel ist für Einsteiger, welche die Grundlagen gelernt haben. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Karateset (Gegenstand 3 von 10).",
"eyewearMystery202312Notes": "Kein Grund zur Sorge, diese eisigen Blautöne helfen dir, hinter der kalten und dunklen Jahreszeit die Wärme der nachfolgenden Monate zu erspähen. Gewährt keinen Attributbonus. Dezemeber 2023 Abonnentengegenstand.",
"eyewearMystery202406Notes": "Versuch zu vermeiden, dass dies von einer Bande aufdringlicher Kinder und ihrem sprechenden Hund abgezogen wird. Gewährt keinen Attributbonus. Juni 2024 Abonnentengegenstand.",
"bodyArmoireKarateOrangeBeltText": "Orangener Gürtel",
"headAccessoryMystery202305Text": "Abendzeitliche Hörner",
"eyewearMystery202303Notes": "Vermittle deinen Feinden durch deinen lässigen Gesichtsausdruck ein falsches Gefühl der Sicherheit. Gewährt keinen Attributbonus. März 2023 Abonnentengegenstand.",
"eyewearMystery202308Notes": "Bist du schläfrig oder ruhst du deine Augen nur in Erwartung deines nächsten tollen Kampfes aus? Gewährt keinen Attributbonus. August 2023 Abonnentengegenstand.",
"bodyArmoireKarateWhiteBeltText": "Weißer Gürtel",
"bodyArmoireKarateWhiteBeltNotes": "Dieser niedrigste Gürtel ist für jene, die ihre Reise gerade erst beginnen. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Karateset (Gegenstand 2 von 10).",
"bodyArmoireKarateGreenBeltNotes": "Dieser Gürtel ist für diejenigen gedacht, die auf fortgeschrittenem Niveau lernen, ihre Fähigkeiten zu verbessern. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Karateset (Gegenstand 5 von 10).",
"bodyArmoireKarateBlueBeltNotes": "Dieser Gürtel ist für diejenigen, die mehr lernen und ihren Geist und Körper entwickeln wollen. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Karateset (Gegenstand 6 von 10).",
"headAccessoryMystery202302Notes": "Das schnurrige Accessoire, das dein bezauberndes Grinsen unterstreicht. Gewährt keinen Attributbonus. Februar 2023 Abonnentengegenstand.",
"headAccessoryMystery202307Notes": "Dieser mächtige Stirnreif beschwört Wirbelstürme und stürmisches Wetter herauf! Gewährt keinen Attributbonus. Juli 2023 Abonnentengegenstand.",
"headAccessoryMystery202305Notes": "Diese Hörner leuchten durch das reflektierte Mondlicht. Gewährt keinen Attributbonus. Mai 2023 Abonnentengegenstand.",
"bodyArmoireKarateYellowBeltText": "Gelber Gürtel",
"bodyArmoireKaratePurpleBeltNotes": "Dieser Gürtel ist für diejenigen, die für anspruchsvolle Übungen bereit sind. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Karateset (Gegenstand 7 von 10).",
"bodyArmoireKarateRedBeltText": "Roter Gürtel",
"bodyArmoireKarateRedBeltNotes": "Dieser Gürtel ist für diejenigen, die gelernt haben, bei ihren Übungen vorsichtig vorzugehen. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Karateset (Gegenstand 8 von 10).",
"bodyArmoireKarateBlackBeltNotes": "Dieser höchste Gürtelgrad ist für diejenigen, die ein tieferes Verständnis anstreben und ihr Wissen an andere weitergeben dürfen. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Karateset (Gegenstand 10 von 10).",
"headAccessorySpecialHeroicCircletText": "Heldenhafter Stirnreif",
"headAccessorySpecialHeroicCircletNotes": "Schwer ist das Haupt, das die Krone trägt, aber dieser Stirnreif ist so leicht wie dein großzügiger Geist. Erhöht alle Werte um <%= attrs %>.",
"headAccessoryMystery202309Text": "Kolossale Kometenmotten-Antennen",
"headAccessoryMystery202309Notes": "Diese Antennen sind modisch und gefiedert, helfen aber auch bei der Navigation! Gewährt keinen Attributbonus. September 2023 Abonnentengegenstand.",
"headAccessoryMystery202310Text": "Geisterlicht-Krone",
"headAccessoryMystery202310Notes": "Wie ein Irrlicht können diese unheimlichen Lichter neugierige Seelen in ihr Verderben locken. Gewährt keinen Attributbonus. Oktober 2023 Abonnentengegenstand.",
"eyewearSpecialAnniversaryNotes": "Schau durch die Augen eines Habitica-Helden - durch deine! Gewährt keinen Attributbonus. Sonderausgaben-Gegenstand zur 10. Geburtstagsfeier.",
"bodyArmoireKarateBlueBeltText": "Blauer Gürtel",
"bodyArmoireKaratePurpleBeltText": "Violetter Gürtel",
"eyewearMystery202303Text": "Verträumte Augen",
"eyewearSpecialAnniversaryText": "Habiticas Heldenmaske"
"headAccessoryMystery202212Text": "Eis-Tiara"
}

View File

@@ -25,7 +25,7 @@
"user": "Benutzer",
"market": "Marktplatz",
"newSubscriberItem": "Du hast einen neuen <span class=\"notification-bold-blue\">Überraschungsgegenstand</span>",
"subscriberItemText": "Abonnenten bekommen jeden Monatsanfang ein neues Überraschungsausrüstungsset!",
"subscriberItemText": "Abonnenten bekommen jeden Monat einen Überraschungsgegenstand. Er wird Anfang des Monats verfügbar. Schaue auf der 'Überraschungsgegenstand'-Seite des Wikis für mehr Informationen nach.",
"all": "Alle",
"none": "Keine",
"more": "<%= count %> mehr",
@@ -189,7 +189,7 @@
"dismissAll": "Alle entfernen",
"messages": "Nachrichten",
"emptyMessagesLine1": "Du hast im Moment keine Nachrichten",
"emptyMessagesLine2": "Sende eine Nachricht, um eine Unterhaltung mit deinen Partymitgliedern oder anderen Habitica-Spieler*innen zu beginnen",
"emptyMessagesLine2": "Du kannst anderen eine neue Nachricht schicken, indem Du ihr Profil aufrufst und auf den \"Nachrichten\"-Knopf drückst.",
"userSentMessage": "<span class=\"notification-bold\"><%- user %></span> hat Dir eine Nachricht gesendet",
"letsgo": "Auf geht's!",
"selected": "Ausgewählt",
@@ -238,8 +238,5 @@
"mutePlayer": "Stumm",
"skipExternalLinkModal": "Halte STRG (Windows) oder Command (Mac) beim Anklicken eines Links, um dieses Modal zu überspringen.",
"shadowMute": "Unsichtbare Stummschaltung",
"titleCustomizations": "Individualisierungen",
"newMessage": "Neue Nachricht",
"targetUserNotExist": "Benutzer: '<%= userName %>' existiert nicht.",
"rememberToBeKind": "Denk bitte daran, freundlich und respektvoll zu sein und die <a href='/static/community-guidelines' target='_blank'>Community-Richtlinien</a> einzuhalten."
"titleCustomizations": "Individualisierungen"
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Grund für die Beschwerde",
"optional": "Optional",
"needsTextPlaceholder": "Gib Deine Nachricht hier ein.",
"copyMessageAsToDo": "Nachricht als To-Do übernehmen",
"copyAsTodo": "Als To-Do kopieren",
"messageAddedAsToDo": "Nachricht als To-Do übernommen.",
"leaderOnlyChallenges": "Nur die Gruppenleitung kann Herausforderungen erstellen",
"sendGift": "Ein Geschenk schicken",
"inviteFriends": "Lade Freunde ein",
@@ -242,7 +245,7 @@
"guildSummaryPlaceholder": "Schreibe eine Kurzbeschreibung über deine Gruppe.. Was ist der Hauptzweck der Gruppe und was werden die Gruppenmitglieder tun?",
"groupDescription": "Beschreibung",
"guildDescriptionPlaceholder": "Nutze diesen Abschnitt um alles, was Mitglieder über Deine Gruppe wissen sollten, ausführlicher darzustellen. Nützliche Tipps, hilfreiche Links und ermutigende Worte gehören hier hin!",
"markdownFormattingHelp": "[Markdown Formatierungshilfe](https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica)",
"markdownFormattingHelp": "[Markdown Formatierungshilfe](https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet)",
"partyDescriptionPlaceholder": "Das ist unsere Partybeschreibung. Sie beschreibt, was wir in unserer Party so tun. Wenn Du mehr darüber wissen willst, was wir in unserer Party so machen, lies die Beschreibung. Party on!",
"guildGemCostInfo": "Eine Edelstein-Gebühr fördert die Qualität der Gilden und wird der Gildenbank gutgeschrieben.",
"noGuildsTitle": "Du bist nicht Mitglied einer Gilde.",
@@ -427,6 +430,5 @@
"createGroupTitle": "Erstelle Gruppe",
"readyToUpgrade": "Bereit zum Aufrüsten?",
"interestedLearningMore": "Willst du mehr erfahren?",
"checkGroupPlanFAQ": "Schau in die <a href='/static/faq#what-is-group-plan'>Gruppenpläne FAQ</a> um herauszufinden, wie du deine gemeinsamen Aufgaben optimal nutzen kannst.",
"messageCopiedToClipboard": "Nachricht in Zwischenablage kopiert"
"checkGroupPlanFAQ": "Schau in die <a href='/static/faq#what-is-group-plan'>Gruppenpläne FAQ</a> um herauszufinden, wie du deine gemeinsamen Aufgaben optimal nutzen kannst."
}

View File

@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Dieser Gegenstand kann nicht in größeren Mengen als 1 gekauft werden.",
"notificationsRequired": "Mitteilungs-IDs werden benötigt.",
"unallocatedStatsPoints": "Du kannst <span class=\"notification-bold-blue\"><%= points %> Attributpunkt(e)</span> verteilen",
"beginningOfConversation": "Dies ist der Anfang Deiner Unterhaltung mit <%= userName %>.",
"messageDeletedUser": "Tut uns leid, dieser Benutzer hat sein Konto gelöscht.",
"messageMissingDisplayName": "Fehlender Anzeigename.",
"reportedMessage": "Du hast diese Nachricht den Moderatoren gemeldet.",
"canDeleteNow": "Du kannst diese Nachricht nun löschen, wenn Du willst.",
"beginningOfConversationReminder": "Denke an einen freundlichen und respektvollen Umgang und halte Dich an die Community-Richtlinien!",
"newsPostNotFound": "News-Eintrag nicht gefunden, oder Du hast keinen Zugriff.",
"messagePetMountUnEquipped": "Haus- und Reittier in die Stallungen gebracht.",
"messageCostumeUnEquipped": "Kostüm abgelegt.",

View File

@@ -126,7 +126,7 @@
"limitedAvailabilityHours": "Für t <%= hours %>std und <%= minutes %>min verfügbar",
"limitedAvailabilityDays": "Für <%= days %>t <%= hours %>std und <%= minutes %>min verfügbar",
"amountExp": "<%= amount %> Exp",
"helpSupportHabitica": "Hilf dabei, Habitica zu unterstützen",
"helpSupportHabitica": "Hilf Habitica zu unterstützen",
"groupsPaymentSubBilling": "Dein nächstes Rechnungsdatum ist <strong><%= renewalDate %></strong>.",
"groupsPaymentAutoRenew": "Dieses Abonnement läuft automatisch weiter, bis es gekündigt wird. Du kannst es im Gruppen-Abrechnungs-Tab kündigen.",
"sellItems": "Items verkaufen",

View File

@@ -786,7 +786,7 @@
"questChameleonNotes": "Es ist ein schöner Tag in einer warmen, regnerischen Ecke der Aufgabenwälder. Du bist auf der Jagd nach Neuzugängen für deine Blattsammlung, als ein Ast vor dir ohne Vorwarnung seine Farbe ändert! Und dann bewegt er sich!<br><br>Rückwärts stolpernd realisierst du, dass dies überhaupt kein Ast ist, sondern ein großes Chamäleon! Jeder Teil seines Körpers wechselt andauernd seine Farbe, während seine Augen in unterschiedliche Richtungen zucken.<br><br>“Geht es dir gut?“ fragst du das Chamäleon.<br><br>“Ahhh, na ja,“ sagt es und wirkt ein wenig durcheinander. „Ich habe versucht, mich anzupassen… aber es ist so überwältigend… die Farben kommen und gehen ständig! Es ist schwer, sich auf nur eine zu konzentrieren….“<br><br>“Aha,“ sagst du, „Ich glaube, ich kann helfen. Wir schärfen deine Konzentration mit einer kleinen Herausforderung! Halte deine Farben bereit!“<br><br>“Die Wette gilt!“ erwidert das Chamäleon.",
"questGiraffeBoss": "Gear-affe",
"questGiraffeCompletion": "Nachdem du der Gear-affe mit ein bisschen grundlegender Organisation ihres Stapels geholfen hast, fühlt ihr euch beide energiegeladener und motivierter!<br><br>Sie nimmt ihre Gitarre und ein Heft mit Anfängerübungen und spielt ein paar Noten. \"Es fühlt sich gut an, einen Schritt in die richtige Richtung zu machen, selbst wenn es nur ein kleiner ist. Vielen Dank, dass du mir geholfen hast! Nimm diese hier, ich habe gehört, du hast einige Haustiere und diese Kameraden könnten eine nette Ergänzung sein!\"",
"questCrabDropCrabEgg": "Krabbe (Ei)",
"questCrabDropCrabEgg": "Kabbe (Ei)",
"questCrabUnlockText": "Schaltet Krabbeneier zum Kauf auf dem Marktplatz frei.",
"questChameleonCompletion": "Nach ein paar lebhaften Drehungen durchlief das Chamäleon alle Farben des Regenbogens und traf perfekt alle Farben, die du verlangt hattest.<br><br>\"Wow,\" sagt es, \"zusammenzuarbeiten, und es zu einem Spiel zu machen, hat mir wirklich geholfen, mich zu konzentrieren! Bitte nimm diese als Belohnung, du hast sie verdient! Bring diesen kleinen Jungen bei, wie man in alle Regenbogenfarben wechselt, wenn sie schlüpfen.\"",
"questCrabNotes": "Es ist ein warmer, sonniger Morgen, und Du genießt einen Besuch am Strand, um ein paar Bücher von Deiner Sommerleseliste zu lesen. Du schreckst auf, als du fast auf einen glänzenden Kristall in der Nähe eines flachen Lochs im Sand trittst.<br><br>„Ey, pass auf, wo du hingehst! Ich baue hier eine Wohnhöhle!“, sagt eine Stimme. Eine überraschend große Krabbe mit einem dekorativen Panzer buddelt sich vor Deinen Zehen aus dem Loch und schnappt mit ihrer Schere, während sie spricht.<br><br>„Hm, ist das eine Höhle?“, fragst Du und betrachtest die flache Vertiefung. Es sind Muscheln und Kristalle um sie herum angeordnet, aber es deutet nicht viel auf einen Rückzugsort hin.<br><br>Die Krabbe stottert. „Ey, das ist eine vorurteilsfreie Zone! Ich komme schon noch dazu, ich komme schon noch dazu... Ich bin gerade beim Dekorieren hängen geblieben. Manchmal muss eine Krabbe eben ein wenig Zeit vertrödeln“, sagt sie und rückt eine Schale zurecht.<br><br>„Warum hilfst Du nicht mit, wenn Du schon so großartige Vorstellungen davon hast, wie eine Höhle aussehen soll?“",

View File

@@ -45,7 +45,7 @@
"confirmPass": "Neues Passwort bestätigen",
"newUsername": "Neuer Benutzername",
"dangerZone": "Gefahrenzone",
"resetText1": "<b>Sei vorsichtig!</b> 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.",
"resetText1": "Sei vorsichtig! 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": "Eine andere Möglichkeit ist die Verwendung einer <b>Sphäre der Wiedergeburt</b>, die alles andere zurücksetzt, während deine Aufgaben und Ausrüstung erhalten bleiben.",
"deleteLocalAccountText": "<b>Bist Du sicher?</b> Dies wird Dein Konto für immer löschen und es kann nicht wiederhergestellt werden! Wenn Du Habitica wieder verwenden möchtest, musst Du ein neues Konto registrieren. Gesparte oder verbrauchte Edelsteine werden nicht ersetzt. Wenn Du absolut sicher bist, dann tippe Dein Passwort in das Textfeld unten ein.",
"deleteSocialAccountText": "<b>Bist Du sicher?</b> Dies wird Dein Konto für immer löschen und es kann nicht wiederhergestellt werden! Wenn Du Habitica wieder verwenden möchtest, musst Du ein neues Konto registrieren. Gesparte oder verbrauchte Edelsteine werden nicht ersetzt. Wenn Du absolut sicher bist, dann tippe <b>\"<%= magicWord %>\"</b> in das Textfeld unten ein.",
@@ -188,7 +188,7 @@
"transaction_release_pets": "Haustiere freigelassen",
"transaction_release_mounts": "Reittiere freigelassen",
"addPasswordAuth": "Passwort hinzufügen",
"nextHourglassDescription": "Abonnierende erhalten eine Mystische Sanduhr, ein Mystisches Ausrüstungsset und Edelsteine, die innerhalb der ersten zwei Tage des Monats auf dem Markt wieder aufgefüllt werden",
"nextHourglassDescription": "Abonnierende erhalten eine Mystische Sanduhr, ein Mystisches Ausrüstungsset und Edelsteine, die innerhalb der ersten zwei Tage des Monats auf dem Markt wieder aufgefüllt werden.",
"gemCap": "Edelsteinobergrenze",
"nextHourglass": "Nächste Lieferung einer Mystischen Sanduhr",
"adjustment": "Änderung",

View File

@@ -1011,10 +1011,6 @@
"backgroundWinterLandscapeWithCabinText": "Winter Landscape with Cabin",
"backgroundWinterLandscapeWithCabinNotes": "Stay cozy in a Winter Landscape with a Cabin.",
"backgrounds022025": "SET 129: Released February 2025",
"backgroundOldFashionedTeaShopText": "Old Fashioned Tea Shop",
"backgroundOldFashionedTeaShopNotes": "Enjoy a cozy beverage in an Old Fashioned Tea Shop.",
"timeTravelBackgrounds": "Steampunk Backgrounds",
"backgroundAirshipText": "Airship",
"backgroundAirshipNotes": "Become a sky sailor on board your very own Airship.",

View File

@@ -271,10 +271,6 @@
"questEggDogMountText": "Dog",
"questEggDogAdjective": "a friendly",
"questEggCatText": "Kitten",
"questEggCatMountText": "Cat",
"questEggCatAdjective": "a mischievous",
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Base",

View File

@@ -1453,8 +1453,6 @@
"armorMystery202407Notes": "Glide through lakes and canals with your sweeping pink tail! Confers no benefit. July 2024 Subscriber Item.",
"armorMystery202412Text": "Candy Cane Cottontail Coat",
"armorMystery202412Notes": "A fun and fluffy look to keep you snug on a winter day. Confers no benefit. December 2024 Subscriber Item.",
"armorMystery202502Text": "Heartfelt Harlequin Suit",
"armorMystery202502Notes": "Youre full of kind-hearted jokes and japes from your ruffled collar to your gigantic shoes! Confers no benefit. February 2025 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
@@ -2374,8 +2372,6 @@
"headMystery202412Notes": "Warm and cozy, just like a cup of minty hot cocoa on a winter night! Confers no benefit. December 2024 Subscriber Item.",
"headMystery202501Text": "Frostbinders Hat",
"headMystery202501Notes": "This sparkling hat generates a light and festive flurry around you at all times. Confers no benefit. January 2025 Subscriber Item.",
"headMystery202502Text": "Heartfelt Harlequin Hat",
"headMystery202502Notes": "This jaunty little hat is sure to inspire joy in anyone who sees you! Confers no benefit. February 2025 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
@@ -2584,8 +2580,6 @@
"headArmoireFestiveHelperHatNotes": "Holiday tip #27: have a helper hat handy. This one is big enough to keep an emergency toy underneath! Increases Intelligence by <%= int %>. Enchanted Armoire: Festive Helper Set (Item 1 of 2)",
"headArmoireSnowyTrapperHatText": "Snowy Trapper Hat",
"headArmoireSnowyTrapperHatNotes": "Blue, frostbitten ears will be a thing of the past. Embrace cozy warmth in style! Increases Constitution and Perception by <%= attrs %> each. Enchanted Armoire: Snowy Trapper Hat Set (Item 1 of 2).",
"headArmoireFancyFloralHatText": "Fancy Floral Hat",
"headArmoireFancyFloralHatNotes": "Feast your eyes on this fancy hat full of fantastic flowers and frilly fastenings. Increases Intelligence by <%= int %>. Enchanted Armoire: Fancy Floral Accessories Set (Item 1 of 2).",
"offhand": "off-hand item",
"offHandCapitalized": "Off-Hand Item",
@@ -2901,8 +2895,6 @@
"shieldMystery202409Notes": "The glowing ruby on this staff draws its power from the late summer sun. Confers no benefit. September 2024 Subscriber Item.",
"shieldMystery202501Text": "Frostbinder's Staff",
"shieldMystery202501Notes": "Decorate any outdoor scenery with a diamond coat of shimmering frost. Confers no benefit. January 2025 Subscriber Item.",
"shieldMystery202502Text": "Heartfelt Harlequin Balloons",
"shieldMystery202502Notes": "This Valentines Day and every day, may your heart be as light as these buoyant balloons. Confers no benefit. February 2025 Subscriber Item.",
"shieldMystery301405Text": "Clock Shield",
"shieldMystery301405Notes": "Time is on your side with this towering clock shield! Confers no benefit. June 3015 Subscriber Item.",
@@ -3071,8 +3063,6 @@
"shieldArmoireBuoyantBeachBallNotes": "Got too many balls up in the air already? Heres one that you can safely set down, roll, bounce and bounce and bounce... Increases Strength by <%= str %>. Enchanted Armoire: Beachside Set (Item 4 of 4).",
"shieldArmoireSafetyFlashlightText": "Safety Flashlight",
"shieldArmoireSafetyFlashlightNotes": "Wait, did you hear that noise? Quick! Shine your flashlight into the shadows over there. Hmmm. Looks like it was just the wind. Or was it…? Increases Constitution by <%= con %>. Enchanted Armoire: Fright Night Set (Item 1 of 2)",
"shieldArmoireFancyFloralFanText": "Fancy Floral Fan",
"shieldArmoireFancyFloralFanNotes": "Finish your fancy look with this first-rate fan made of fabulous floral fabric. Increases Perception by <%= per %>. Enchanted Armoire: Fancy Floral Accessories Set (Item 2 of 2).",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",

View File

@@ -207,7 +207,8 @@
"dismissAll": "Dismiss All",
"messages": "Messages",
"emptyMessagesLine1": "You don't have any messages",
"emptyMessagesLine2": "You can send a new message to a user by visiting their profile and clicking the \"Message\" button.",
"emptyMessagesLine2": "Send a message to start a conversation with your Party members or another Habitica player",
"newMessage": "New Message",
"userSentMessage": "<span class=\"notification-bold\"><%- user %></span> sent you a message",
"letsgo": "Let's Go!",
"selected": "Selected",
@@ -238,5 +239,7 @@
"submitQuestion": "Submit Question",
"whyReportingPlayer": "Why are you reporting this player?",
"whyReportingPlayerPlaceholder": "Reason for report",
"playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habiticas Community Guidelines."
"playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habiticas Community Guidelines.",
"targetUserNotExist": "Target User: '<%= userName %>' does not exist.",
"rememberToBeKind": "Please remember to be kind, respectful, and follow the <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>."
}

View File

@@ -114,9 +114,7 @@
"whyReportingPostPlaceholder": "Reason for report",
"optional": "Optional",
"needsTextPlaceholder": "Type your message here.",
"copyMessageAsToDo": "Copy message as To Do",
"copyAsTodo": "Copy as To Do",
"messageAddedAsToDo": "Message copied as To Do.",
"messageCopiedToClipboard": "Message copied to clipboard.",
"leaderOnlyChallenges": "Only group leader can create challenges",
"sendGift": "Send a Gift",
"selectGift": "Select Gift",

View File

@@ -51,8 +51,6 @@
"messageNotAbleToBuyInBulk": "This item cannot be purchased in quantities above 1.",
"notificationsRequired": "Notification ids are required.",
"unallocatedStatsPoints": "You have <span class=\"notification-bold-blue\"><%= points %> unallocated Stat Points</span>",
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>.",
"beginningOfConversationReminder": "Remember to be kind, respectful, and follow the Community Guidelines!",
"messageDeletedUser": "Sorry, this user has deleted their account.",
"messageMissingDisplayName": "Missing display name.",
"reportedMessage": "You have reported this message to moderators.",

View File

@@ -933,17 +933,6 @@
"questDogDropDogEgg": "Dog (Egg)",
"questDogUnlockText": "Unlocks Dog Eggs for purchase in the Market.",
"questCatText": "A Purrplexing Predicament",
"questCatNotes": "On this fine day you find yourself in Habit City's Enchanted Efficiency Emporium workshop. You've been assigned a tough task: create a new magic motivation spell to help Habiticans everywhere complete their goals with ease.<br><br>Sitting on a table in front of you is a variety of magical objects. All the tomes said they were supposed to resonate together with productive energy… but so far there's not even a spark of motivation.<br><br>The creaking of a door alerts you to a new guest entering your workshop. Scampering feet and a blur of fluff dart onto the table. A... cat? Before you even have a chance to compliment how fluffy she is, she's lifting a paw to one of the crystals you set up and… knocking it off the table!<br><br>\"Hey!\" you shout, \"You're really cute but I'm trying to do some work over here...\"<br><br>She looks at you with her pretty blue eyes, tilts her head, and bats a bundle of herbs off the table. \"I'm helping!\" she purrs.<br><br>You see her paw reaching out toward the rest of the items you've collected and dive to the floor to catch the next one to go down!",
"questCatCompletion": "You've thankfully caught everything that pushy cat knocked off the table. As you sit on the floor you notice a bright glow coming from the objects in front of you. Looking up, the ones on the table are reacting too! Putting them at different elevations seems to be a breakthrough in your research!<br><br>\"You know, in the end you did help me. I guess I just needed some fresh eyes on my task to get me unstuck. I wish you would have given me a bit of a heads-up before you started pushing things around, though,\" you say to the cat, patting her gently.<br><br>\"That's a purrfectly reasonable request, please take these as my apology!\" she purrs, nudging some funny-looking eggs in your direction. \"I'm glad I could help you see things from a different purrspective.\"",
"questCatBoss": "The Purrplexer",
"questCatRageTitle": "Furious Bapping",
"questCatRageDescription": "This bar fills when you don't complete your Dailies. When it's full, the Purrplexer will take away some of your party's MP!",
"questCatRageEffect": "The Purrplexer knocks the magic objects you've collected off the table! The party's MP is reduced!",
"questCatDropCatEgg": "Cat (Egg)",
"questCatUnlockText": "Unlocks Cat Eggs for Purchase in the Market.",
"questFungiText": "The Moody Mushroom",
"questFungiNotes": "Its been a rainy spring in Habitica and the ground around the stables is spongy and damp. You notice quite a few mushrooms have appeared along the wooden stable walls and fences. Theres a fog hanging about, not quite letting the sun peek through, and its a bit dispiriting.<br><br>Out of the mist you see the outline of the April Fool, not at all his usual bouncy self.<br><br>”Id hoped to bring you all some delightful Fungi Magic Hatching Potions so that you can keep your mushroom friends from my special day forever,” he says, his expression alarmingly unsmiling. “But this cold fog is really getting to me, its making me feel too tired and dismal to work my usual magic.”<br><br>“Oh no, sorry to hear that,” you say, noticing your own increasingly somber mood. “This fog is really making the day gloomy. I wonder where it came from…”<br><br>A low rumble sounds across the fields, and you see an outline emerging from the mist. Youre alarmed to see a gigantic and unhappy looking mushroom creature, and the mist appears to be emanating from it.<br><br>“Aha,” says the Fool, “I think this fungal fellow may be the source of our blues. Lets see if we can summon a little cheer for our friend here and ourselves.”",
"questFungiCompletion": "You and the April Fool look at each other with a sign of relief as the mushroom retreats to the forest.<br><br>“Ah,” the Fool exclaims, “that was quite a mycelial melancholy. Im glad we could improve his mood, and ours too! I feel my energy coming back. Come with me and well get those Fungi potions going together.”",

View File

@@ -170,7 +170,6 @@
"mysterySet202411": "Bristled Brawler Set",
"mysterySet202412": "Candy Cane Cottontail Set",
"mysterySet202501": "Frostbinder Set",
"mysterySet202502": "Heartfelt Harlequin Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySet301703": "Peacock Steampunk Set",

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Reason for report",
"optional": "Optional",
"needsTextPlaceholder": "Type your message here.",
"copyMessageAsToDo": "Copy message as To Do",
"copyAsTodo": "Copy as To Do",
"messageAddedAsToDo": "Message copied as To Do.",
"leaderOnlyChallenges": "Only the group leader can create challenges",
"sendGift": "Send a Gift",
"inviteFriends": "Invite Friends",

View File

@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "This item cannot be purchased in quantities above 1.",
"notificationsRequired": "Notification ids are required.",
"unallocatedStatsPoints": "You have <span class=\"notification-bold-blue\"><%= points %> unallocated Stat Points</span>",
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>.",
"messageDeletedUser": "Sorry, this user has deleted their account.",
"messageMissingDisplayName": "Missing display name.",
"reportedMessage": "You have reported this message to moderators.",
"canDeleteNow": "You can now delete the message if you wish.",
"beginningOfConversationReminder": "Remember to be kind, respectful, and follow the Community Guidelines!",
"newsPostNotFound": "News Post not found or you dont have access.",
"messageAllUnEquipped": "Everything unequipped.",
"messageBackgroundUnEquipped": "Background unequipped.",

View File

@@ -25,7 +25,7 @@
"user": "Usuario",
"market": "Mercado",
"newSubscriberItem": "Tienes <span class=\"notification-bold-blue\">Objetos Misteriosos</span> nuevos",
"subscriberItemText": "¡Los suscriptores reciben una nueva colección de equipamiento misterioso al principio de cada mes!",
"subscriberItemText": "Cada mes, los suscriptores recibirán un objeto misterioso, que estará disponible al comienzo del mes. Para más información, consulta la página de la wiki sobre los objetos misteriosos.",
"all": "Todo",
"none": "Ninguno",
"more": "<%= count %> más",
@@ -189,7 +189,7 @@
"dismissAll": "Ignorar todas",
"messages": "Mensajes",
"emptyMessagesLine1": "No tienes ningún mensaje",
"emptyMessagesLine2": "Envía un mensaje para empezar una conversación con los miembros de tu Equipo o con otro jugador de Habitica",
"emptyMessagesLine2": "Puedes enviar un nuevo mensaje a un usuario visitando su perfil y presionando \"Mensaje\".",
"userSentMessage": "<span class=\"notification-bold\"><%- user %></span> te ha enviado un mensaje",
"letsgo": "¡Vamos!",
"selected": "Seleccionado",
@@ -238,8 +238,5 @@
"whyReportingPlayer": "¿Por qué informas acerca de este jugador?",
"reportPlayer": "Informar acerca de un Jugador",
"viewAdminPanel": "Ver Comandos de Administrador",
"playerReportModalBody": "Solo deberías informar de un jugador que de alguna forma quebrantara las <%= firstLinkStart %>Normas de la Comunidad<%= linkEnd %> y/o <%= secondLinkStart %>los Terminos de Servicio<%= linkEnd %>. Enviar un informe falso es una clara violación de la Normas de la Comunidad de Habitica.",
"targetUserNotExist": "Usuario Objetivo: '<%= userName %>' no existe.",
"rememberToBeKind": "Por favor recuerda ser amable y respetuoso, y sigue las <a href='/static/community-guidelines' target='_blank'>Normas de la Comunidad</a>.",
"newMessage": "Nuevo Mensaje"
"playerReportModalBody": "Solo deberías informar de un jugador que de alguna forma quebrantara las <%= firstLinkStart %>Normas de la Comunidad<%= linkEnd %> y/o <%= secondLinkStart %>los Terminos de Servicio<%= linkEnd %>. Enviar un informe falso es una clara violación de la Normas de la Comunidad de Habitica."
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Motivo del informe",
"optional": "Opcional",
"needsTextPlaceholder": "Escribe tu mensaje aquí.",
"copyMessageAsToDo": "Copiar mensaje como Tarea Pendiente",
"copyAsTodo": "Copiar como Tarea Pendiente",
"messageAddedAsToDo": "Mensaje copiado como Tarea pendiente.",
"leaderOnlyChallenges": "Sólo el Líder del Grupo puede crear desafíos",
"sendGift": "Enviar un regalo",
"inviteFriends": "Invitar Amigos",
@@ -242,7 +245,7 @@
"guildSummaryPlaceholder": "Escribe una breve descripción de tu Grupo. ¿Cuál es el propósito principal de tu Grupo y qué harán sus miembros?",
"groupDescription": "Descripción",
"guildDescriptionPlaceholder": "Usa esta sección para desarrollar más en detalle todo lo que los miembros del Grupo deberían saber sobre ella. ¡Los consejos de utilidad, enlaces de interés y frases motivacionales van aquí!",
"markdownFormattingHelp": "[Ayuda con el formato Markdown](https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica)",
"markdownFormattingHelp": "[Ayuda con el formato Markdown](https://habitica.fandom.com/es/wiki/Gu%C3%ADa_de_Markdown)",
"partyDescriptionPlaceholder": "Esta es la descripción de nuestro Equipo. Explica lo que hacemos en este Equipo. Si quieres saber más sobre lo que hacemos en este Equipo, lee la descripción. Toma equipo.",
"guildGemCostInfo": "El coste de Gemas promueve una alta calidad en los Gremios y es transferido al banco de tu Gremio.",
"noGuildsTitle": "No eres miembro de ningún Gremio.",
@@ -427,6 +430,5 @@
"createGroupTitle": "Forma un Equipo",
"readyToUpgrade": "¿Estás listo para Mejorar?",
"interestedLearningMore": "¿Te gustaría saber más?",
"checkGroupPlanFAQ": "Echa un vistazo a <a href='/static/faq#what-is-group-plan'>FAQ Planes Grupales</a> para saber cómo obtener el máximo de tu experiencia en tareas compartidas.",
"messageCopiedToClipboard": "Mensaje copiado en el portapapeles."
"checkGroupPlanFAQ": "Echa un vistazo a <a href='/static/faq#what-is-group-plan'>FAQ Planes Grupales</a> para saber cómo obtener el máximo de tu experiencia en tareas compartidas."
}

View File

@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Este artículo no se puede comprar en cantidades superiores a 1.",
"notificationsRequired": "Se requieren ids de notificación.",
"unallocatedStatsPoints": "Tienes <span class=\"notification-bold-blue\"><%= points %> Puntos de Atributo sin asignar</span>",
"beginningOfConversation": "Este es el principio de tu conversación con <%= userName %>.",
"messageDeletedUser": "Lo sentimos, este usuario ha eliminado su cuenta.",
"messageMissingDisplayName": "Falta el nombre público.",
"reportedMessage": "Has reportado este mensaje a los moderadores.",
"canDeleteNow": "Ahora puedes eliminar el mensaje si lo deseas.",
"beginningOfConversationReminder": "¡Recuerda ser amable, respetuoso, y seguir las Normas de la Comunidad!",
"newsPostNotFound": "Publicación de noticia no encontrada o no tienes acceso a ella.",
"messageAllUnEquipped": "Nada equipado.",
"messageBackgroundUnEquipped": "Fondo no equipado.",

View File

@@ -85,8 +85,8 @@
"mountsReleased": "Monturas liberadas",
"welcomeStable": "¡Bienvenido a tus Mascotas y Monturas!",
"welcomeStableText": "¡Bienvenido al establo! Soy Matt, el señor de las bestias. Cada vez que completas una tarea, tienes una chance aleatoria de conseguir un Huevo o una Poción de eclosión con los cuales puedes eclosionar una Mascota. ¡Cuando nazca tu Mascota, aparecerá aquí! Haz clic en la imagen de una Mascota para añadirla a tu personaje. Aliméntalas con el alimento para mascotas que encuentres y se convertirán en vigorosas Monturas.",
"petLikeToEat": "¿Qué le gusta comer a mi Mascota?",
"petLikeToEatText": "Las Mascotas crecerán, sin importar lo que les des de comer, pero crecerán más rápido si se les da de comer el Alimento que más les gusta. Experimenta para encontrar el patrón, o encuentra las respuestas aquí: <br/><a href=\"/static/faq#comida-mascota\" target=\"_blank\">https://habitica. com/static/faq#comidas-mascota </a>",
"petLikeToEat": "¿Qué le gusta comer a mi mascota?",
"petLikeToEatText": "Las Mascotas crecerán, sin importar lo que les des de comer, pero crecerán más rápido si se les da de comer el Alimento que más les gusta. Experimenta para encontrar el patrón, o encuentra las respuestas aquí: <br/><a href=\"https://habitica.fandom.com/es/wiki/Preferencias_de_Comida\" target=\"_blank\">https://habitica.fandom.com/es/wiki/Preferencias_de_Comida</a>",
"filterByStandard": "Estándar",
"filterByMagicPotion": "Poción de Magia",
"filterByQuest": "Misión",

View File

@@ -769,19 +769,5 @@
"backgroundLeafyTreeTunnelText": "Túnel de árboles frondosos",
"backgroundLeafyTreeTunnelNotes": "deambule a través del túnel de arboles frondosos.",
"backgroundSpringtimeShowerText": "Ducha primaveral",
"backgroundSpringtimeShowerNotes": "Mire una ducha primaveral florida.",
"backgrounds052023": "CONJUNTO 108: Publicado en mayo de 2023",
"backgroundInAPaintingText": "En un Cuadro",
"backgroundInAPaintingNotes": "Disfruta de tu creatividad desde dentro de un cuadro.",
"backgroundFlyingOverHedgeMazeText": "Volando sobre un laberinto de setos",
"backgroundCretaceousForestText": "Bosque Cretácico",
"backgroundCretaceousForestNotes": "Disfruta de la vegetación del Bosque Cretácico.",
"backgroundUnderWisteriaText": "Bajo la glicinia",
"backgroundUnderWisteriaNotes": "Relájate bajo la glicina.",
"backgroundFlyingOverHedgeMazeNotes": "Maravíllate mientras vuelas sobre un laberinto de setos.",
"backgrounds062023": "CONJUNTO 109: Publicado en junio de 2023",
"backgroundInAnAquariumText": "En el acuario",
"backgroundInAnAquariumNotes": "Date un baño tranquilo con los peces En el acuario.",
"backgroundInsideAdventurersHideoutText": "En la guarida de un aventurero",
"backgroundInsideAdventurersHideoutNotes": "Planea tu travesía en la Guarida de un Aventurero."
"backgroundSpringtimeShowerNotes": "Mire una ducha primaveral florida."
}

View File

@@ -7,7 +7,7 @@
"commGuidePara003": "Estas reglas aplican a todos los espacios sociales que usamos, incluyendo (aunque no exclusivamente) Trello, GitHub, Weblate, y la Wiki de Habitica en Fandom. A medida de que las comunidades crecen y cambian, sus reglas pueden adaptarse con el tiempo. ¡Cuando haya cambios sustanciales a estas Reglas, lo sabrás por medio de un anuncio de Bailey y/o nuestras redes sociales!",
"commGuideHeadingInteractions": "Interacciones en Habitica",
"commGuidePara015": "Habitica tiene dos tipos de espacios sociales: públicos y privados. Los espacios públicos incluyen la Taberna, Gremios Públicos, GitHub, Trello y la Wiki. Los espacios privados son Gremios Privados, chat de Equipo y Mensajes Privados. Todos los Nombres Públicos y @nombresdeusuario deben cumplir con las normas de espacio publico. Para cambiar tu Nombre Público o @nombredeusuario, en dispositivos móviles ve a Menú > Ajustes > Perfil; en la página web ve a Usuario > Ajustes.",
"commGuidePara016": "Al navegar por los componentes sociales en Habitica, hay algunas reglas generales para mantener a todos seguros y felices.",
"commGuidePara016": "Al navegar los componentes sociales en Habitica, hay algunas reglas generales para mantener a todos seguros y felices.",
"commGuideList02A": "<strong>Respétense unos a los otros</strong>. Sé cortés, amable , amigable y servicial. Recuerda: los Habiticanos vienen de todo tipo de contextos y han tenido experiencias muy diferentes. ¡Esto es parte de lo que hace a Habitica tan genial! Construir una comunidad significa respetar y celebrar tanto nuestras diferencias como nuestras similitudes.",
"commGuideList02C": "<strong>No publiques imágenes o textos que sean violentos, amenazantes, sexualmente explícitos o sugestivos, o que promuevan la discriminación, intolerancia, racismo, sexismo, odio, acoso o daño hacia cualquier individuo o grupo</strong>. Ni siquiera como una broma o meme. Esto incluye tanto insultos como declaraciones. No todos tienen el mismo sentido del humor, por lo que algo que tú consideres como broma podría ser hiriente para otros.",
"commGuideList02D": "<strong>Mantén las discusiones apropiadas para todas las edades.</strong> Esto significa evitar temas de adultos en espacios públicos. Tenemos muchos Habiticanos jóvenes que usan el sitio, y gente que viene de muchos contextos diferentes. Queremos que nuestra comunidad sea tan cómoda e incluyente como sea posible.",
@@ -23,10 +23,10 @@
"commGuidePara051": "<strong>Hay varios tipos de infracciones, y se tratan dependiendo de su gravedad</strong>. Estas no son listas exhaustivas, y los Mods pueden tomar decisiones en temas que no están cubiertos aquí a su discreción. Los Mods tendrán en cuenta el contexto al evaluar las infracciones.",
"commGuideHeadingSevereInfractions": "Infracciones graves",
"commGuidePara052": "Infracciones severas dañan de forma importante la seguridad de la comunidad y a los usuarios de Habitica, y por lo tanto tienen consecuencias severas.",
"commGuidePara053": "Los siguientes son ejemplos de algunas infracciones severas. Esta no es una lista completa.",
"commGuidePara053": "Los siguientes son ejemplos de algunas infracciones severas. Ésta no es una lista completa.",
"commGuideList05A": "Violación de los Términos y condiciones",
"commGuideList05B": "Comentarios de Odio/imágenes de Odio, Acoso, Ciber-bullying, Mensajes ofensivos, y Provocaciones",
"commGuideList05C": "Violación de Libertad Condicional",
"commGuideList05B": "Comentarios/imágenes difamatorios/as, acoso, ciber-bullying, mensajes ofensivos, y provocaciones",
"commGuideList05C": "Violación de período de prueba",
"commGuideList05D": "Hacerse pasar por miembros del Personal o Moderadores",
"commGuideList05E": "Infracciones moderadas repetidas",
"commGuideList05F": "Creación de una cuenta duplicada para evitar consecuencias (por ejemplo, crear una nueva cuenta para charlar después de que tus privilegios de chat hubieran sido revocados)",

View File

@@ -2482,49 +2482,5 @@
"weaponSpecialSummer2022RogueNotes": "Si estás en aprietos, ¡no dudes en mostrar estas aterradoras pinzas! Incrementa la fuerza en <%= str %>. Equipamiento de edición limitada del verano de 2022.",
"weaponSpecialSummer2022MageNotes": "Limpia mágicamente las aguas delante tuyo con un solo movimiento. Aumenta la inteligencia en <%= int %> y la percepción en <%= per %>. Equipamiento de edición limitada del verano del 2022.",
"weaponSpecialFall2022WarriorNotes": "Tal vez sea más adecuada para cortar troncos u hogazas que la armadura del enemigo... de cualquier forma: ¡GRR! ¡Se ve tan aterradora! Aumenta la fuerza en <%= str %>. Equipamiento de edición limitada del otoño de 2022.",
"weaponMystery202209Text": "Manual Mágico",
"gearItemsCompleted": "¡Ya tienes todo el equipamiento de <%= klass %>! Se publicará nuevo equipamiento en las Galas estacionales.",
"moreArmoireGearAvailable": "¡Hasta entonces, hay aún <%= armoireCount %> piezas de equipamiento que puedes encontrar en el Armario Encantado!",
"moreArmoireGearComing": "¡También en el Armario Encantado tienes nuevos objetos cada mes!",
"weaponSpecialWinter2024RogueText": "Brazalete de Búho de Las Nieves",
"weaponSpecialWinter2024WarriorText": "Pica Caramelo",
"weaponSpecialWinter2024WarriorNotes": "Una buena arma, siempre y cuando puedas evitar comértela. Aumenta la fuerza en <%= str %>. Equipo de edición limitada de invierno 2023-2024.",
"weaponSpecialWinter2024MageText": "Varita de Narval",
"weaponSpecialWinter2024HealerText": "Antorcha",
"weaponSpecialWinter2024RogueNotes": "¡Estás equipado con una ráfaga de plumas y garras! ¡Ululú! Aumenta la fuerza en <%= str %>. Equipo de edición limitada de invierno 2023-2024.",
"weaponSpecialWinter2024MageNotes": "Gracias a un narval generoso y mágico que percibió tus grandes habilidades, has recibido una varita que te permite sentir los cambios que ocurren a tu alrededor. Aumenta la inteligencia en <%= int %>. Edición limitada de invierno 2023-2024.",
"headSpecialNye2022Text": "Gorro de Fiesta Fabuloso",
"headSpecialNye2022Notes": "¡Has recibido un gorro de fiesta fabuloso! Llévalo con orgullo para dar la bienvenida al Año Nuevo! No otorga ningún beneficio.",
"weaponSpecialSummer2023RogueText": "Habanico de Guppy",
"weaponSpecialSummer2023RogueNotes": "No te apresures, estas cosas son difíciles de aprender, ¡pero son impresionantes cuando las aprendes! Aumenta la fuerza en <%= str %>. Edición limitada de equipo de verano 2023.",
"weaponSpecialSummer2023WarriorText": "Espada Elemental de Agua",
"weaponSpecialSummer2023WarriorNotes": "Invoca poderosos chorros de agua para despejar tu camino de obstáculos. Aumenta la fuerza en <%= str %>. Edición limitada de equipo de verano 2023.",
"weaponSpecialSummer2023MageText": "Peces",
"weaponSpecialSummer2023MageNotes": "Estos amigables peces permanecerán a tu lado como los mejores compañeros en el océano. Aumenta la inteligencia en <%= int %>. Edición limitada de artículos de verano 2023.",
"weaponSpecialSummer2023HealerNotes": "Pueden parecer frondosos, pero se ponen bastante gruñones si los llamas \"plantas\". Aumenta la inteligencia en <%= int %>. Edición limitada de equipo de verano 2023.",
"weaponSpecialSummer2023HealerText": "Algas Tambaleantes",
"weaponSpecialSpring2023RogueText": "Hoja mordisqueada",
"weaponSpecialSpring2023RogueNotes": "¡Corte! ¡Aplastamiento! ¡Aperitivo! ¡Hazte fuerte y prepárate para tu próxima metamorfosis! Aumenta la fuerza en <%= str %>. Edición limitada de equipo de primavera 2023.",
"weaponSpecialSpring2023WarriorText": "Lámina de colibrí",
"weaponSpecialSpring2023WarriorNotes": "¡En garde! ¡Ahuyenta a los enemigos de tus flores con este florete! Aumenta la fuerza en <%= str %>. Edición limitada de primavera de 2023.",
"weaponSpecialSpring2023MageText": "Piedra Lunar Mágica",
"weaponSpecialSpring2023MageNotes": "Cuanto mayor sea su brillo, más potente será su poder. Aumenta la inteligencia en <%= int %>. Edición limitada 2023.",
"weaponSpecialSpring2023HealerText": "Polen de lirio",
"weaponSpecialSpring2023HealerNotes": "Con un soplo y una chispa despiertas nuevos brotes, alegría y color. Aumenta la Inteligencia en <%= int %>. Equipamiento de edición limitada de primavera 2023.",
"weaponSpecialWinter2023RogueText": "Banda verde de satén",
"weaponSpecialWinter2023RogueNotes": "Las leyendas hablan de Pícaros que enganchan el arma de sus enemigos, las desarman, y luego les devuelven el objeto, solo para hacerse los simpáticos. Aumenta la fuerza en <%= str %>. Equipamiento de edición limitada de invierno 2022-2023.",
"weaponSpecialWinter2023WarriorText": "Lanza Colmillo",
"weaponSpecialWinter2023MageText": "ZorroFuego",
"weaponSpecialWinter2023WarriorNotes": "Las dos puntas de esta lanza tienen forma de colmillos de morsa, pero son el doble de poderosas. ¡Apuñala las dudas y los poemas tontos hasta que se alejen! Aumenta la fuerza en <%= str %>. Edición limitada 2022-2023 Equipo de invierno.",
"weaponSpecialWinter2023MageNotes": "¡Ni zorro ni fuego, pero sí mucha fiesta! Aumenta la inteligencia en <%= int %> y la percepción en <%= per %>. Edición limitada 2022-2023 Equipo de invierno.",
"weaponSpecialWinter2023HealerText": "Lanza Corona",
"weaponSpecialWinter2023HealerNotes": "Observa cómo esta corona festiva y espinosa gira por el aire hacia tu enemigo o tus obstáculos y regresa hacia ti como un bumerán para que la lances otra vez. Aumenta la inteligencia en <%= int %>. Edición limitada 2022-2023 Equipo de invierno.",
"weaponSpecialFall2023RogueText": "Cuchara Sopanatural",
"weaponSpecialFall2023RogueNotes": "Se necesita un agitador excepcionalmente fuerte para crear burbujas y problemas. Aumenta la fuerza en <%= str %>. Edición limitada de equipo de otoño de 2023.",
"weaponSpecialFall2023WarriorText": "Deliciosas Palomitas",
"weaponSpecialFall2023WarriorNotes": "¡Lo más aterrador de todo es pensar en una noche de película de terror!... ¡Sin aperitivos! Aumenta la fuerza en <%= str %>. Edición limitada de equipo de otoño de 2023.",
"weaponSpecialFall2023MageText": "Bastón Brillante",
"weaponSpecialFall2023MageNotes": "Este bastón brillante, con un cristal en su núcleo, hace que la magia surja de lo mundano. Aumenta la inteligencia en <%= int %>. Edición limitada de otoño de 2023.",
"weaponSpecialFall2023HealerText": "Martillo Tronco Grande",
"weaponSpecialFall2023HealerNotes": "Este martillo retorcido, con ataques lentos y pesados, inflige golpes curativos en lugar de daño. Aumenta la inteligencia en <%= int %>. Edición limitada de equipo de otoño de 2023."
"weaponMystery202209Text": "Manual Mágico"
}

View File

@@ -25,7 +25,7 @@
"user": "Usuario",
"market": "Mercado",
"newSubscriberItem": "Tienes nuevos <span class=\"notification-bold-blue\">Artículos Misteriosos</span>",
"subscriberItemText": "¡Los suscriptores obtienen un nuevo set de equipo misterioso al inicio de cada mes!",
"subscriberItemText": "Cada mes, los suscriptores recibirán un artículo misterioso. Se vuelve disponible al inicio del mes. Ve la página de la wiki \"Artículo Misterioso\" para más información.",
"all": "Todo",
"none": "Ninguno",
"more": "<%= count %> más",

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Ayuda a nuestros moderadores contándonos por qué estás denunciando esta publicación como infracción; por ejemplo, spam, groserías, juramentos religiosos, intolerancia, insultos, temas para adultos, violencia.",
"optional": "Opcional",
"needsTextPlaceholder": "Escribe tu mensaje aqui.",
"copyMessageAsToDo": "Copiar mensaje como Tarea Pendiente",
"copyAsTodo": "Copiar como Tarea Pendiente",
"messageAddedAsToDo": "Mensaje copiado como Tarea Pendiente.",
"leaderOnlyChallenges": "Sólo el líder del grupo puede crear desafíos",
"sendGift": "Enviar un Regalo",
"inviteFriends": "Invitar Amigos",

View File

@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Este artículo no se puede comprar en cantidades arriba de 1.",
"notificationsRequired": "Se requiere el ID de notificación.",
"unallocatedStatsPoints": "Tienes <span class=\"notification-bold-blue\"><%= points %> Puntos de Atributo sin asignar </span>",
"beginningOfConversation": "Este es el comienzo de tu conversación con <%= userName %>.",
"messageDeletedUser": "Lo sentimos, este usuario ha eliminado su cuenta.",
"messageMissingDisplayName": "Nombre de Usuario ausente.",
"canDeleteNow": "Ahora puedes borrar el mensaje si lo desea.",
"reportedMessage": "Has reportado este mensaje a los moderadores.",
"beginningOfConversationReminder": "¡Recuerda de ser amable, respetuoso, y seguir las Normas de la Comunidad!",
"newsPostNotFound": "No se ha encontrado la publicación nueva o no tienes acceso.",
"messageAllUnEquipped": "Todo sin equipar.",
"messageBackgroundUnEquipped": "Fondo no equipado.",

View File

@@ -3101,7 +3101,7 @@
"armorMystery202406Notes": "Hantez vous ennemi·e·s avec élégance et panache ! Ne confère aucun bonus. Équipement d'Abonnement Juin 2024.",
"armorArmoireBlueStripedSwimsuitText": "Maillot de Bain Bleu Rayé",
"armorArmoireBlueStripedSwimsuitNotes": "Qu'est-ce qui peut être plus excitant que de se batte contre des monstres marins à la plage ? Augmente la Constitution de <%= con %>. Armoire Enchantée : Ensemble Tenue de Plage (Objet 2 sur 4).",
"gearItemsCompleted": "Vous possédez tous les équipements pour la classe <%= klass %> ! De nouveaux équipements seront déployés durant les Galas Saisonniers.",
"gearItemsCompleted": "Vous possédez tou les équipements pour la classe <%= klass %> ! De nouveaux équipements seront déployés durant les Galas Saisonniers.",
"moreArmoireGearAvailable": "En attendant, il reste <%= armoireCount %> éléments d'équipement dans l'Armoire Enchantée à découvrir !",
"moreArmoireGearComing": "L'Armoire Enchantée est également remplie d'un nouveau stock tous les mois !",
"weaponSpecialSummer2024RogueNotes": "Utilisez les propres piquants aiguisés de vos ennemi·e·s contre e·ux·lles ! Augmente la Force de <%= str %>. Édition Limitée Équipement Été 2024.",

View File

@@ -25,7 +25,7 @@
"user": "Utilisateur",
"market": "Marché",
"newSubscriberItem": "Vous avez de nouveaux <span class=\"notification-bold-blue\">objets mystère</span>",
"subscriberItemText": "Les Personnes Abonnées reçoivent un nouvel ensemble d'équipement mystère à chaque début de mois !",
"subscriberItemText": "Les personnes abonnées recevront chaque mois un objet mystère. Il est disponible au début du mois. Consultez la page \"Objet mystère\" du wiki pour avoir plus d'information.",
"all": "Tous",
"none": "Aucun",
"more": "<%= count %> de plus",
@@ -189,7 +189,7 @@
"dismissAll": "Tout effacer",
"messages": "Messages",
"emptyMessagesLine1": "Vous n'avez aucun message",
"emptyMessagesLine2": "Envoyez un message pour débuter une conversation avec les membres de votre Équipe ou un·e autre joueu·r·se d'Habitica",
"emptyMessagesLine2": "Vous pouvez envoyer un nouveau message à quelqu'un en regardant leur profil et en cliquant sur le bouton \"Message\".",
"userSentMessage": "<span class=\"notification-bold\"><%- user %></span> vous a envoyé un message",
"letsgo": "Allons-y !",
"selected": "Selectionné",
@@ -238,8 +238,5 @@
"bannedPlayer": "L·e·a Joueu·r·se est banni·e.",
"playerReportModalBody": "Merci de ne signaler que les joueu·r·se· qui enfreignent les <%= firstLinkStart %>Guide de la Communauté<%= linkEnd %> et/ou <%= secondLinkStart %>Conditions d'utilisation<%= linkEnd %>. Déposer un faux signalement est une violation du Guide de la Communauté d'Habitica.",
"unblockPlayer": "Débloquer l·e·a Joueu·r·se",
"titleCustomizations": "Personnalisations",
"newMessage": "Nouveau Message",
"targetUserNotExist": "L'Utilisat·eur·rice : '<%= userName %>' n'existe pas.",
"rememberToBeKind": "N'oubliez pas d'être bienveillant·e, respectueu·x·se, et de suivre le <a href='/static/community-guidelines' target='_blank'>Guide de la Communauté</a>."
"titleCustomizations": "Personnalisations"
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Raison du signalement",
"optional": "Facultatif",
"needsTextPlaceholder": "Écrivez votre message ici.",
"copyMessageAsToDo": "Copier le message comme tâche à faire",
"copyAsTodo": "Copier comme tâche à faire",
"messageAddedAsToDo": "Message copié comme tâche à faire.",
"leaderOnlyChallenges": "Seul le responsable du groupe peut créer des défis",
"sendGift": "Envoyer un cadeau",
"inviteFriends": "Inviter des amis",
@@ -242,7 +245,7 @@
"guildSummaryPlaceholder": "Écrivez une courte explication sur votre Groupe. Quel est l'objectif principal du Groupe et qu'y feront les membres ?",
"groupDescription": "Description",
"guildDescriptionPlaceholder": "Utilisez cette section pour décrire plus en détail ce que les membres devraient savoir à propos du Groupe. Astuces, liens pratiques et commentaires encourageants sont à placer ici !",
"markdownFormattingHelp": "[Aide à la saisie Markdown](https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica)",
"markdownFormattingHelp": "[Aide à la saisie Markdown](https://habitica.fandom.com/fr/wiki/M%C3%A9mo_de_Markdown)",
"partyDescriptionPlaceholder": "Ceci est la description de notre équipe. Elle décrit ce que nous y faisons. Si vous souhaitez en savoir plus, lisez la description. Équipe active.",
"guildGemCostInfo": "Un coût en gemmes permet des guildes de grande qualité, et cette somme est transférée dans la banque de guilde.",
"noGuildsTitle": "Vous n'êtes membre d'aucune guilde.",
@@ -427,6 +430,5 @@
"createGroupTitle": "Créer un Groupe",
"readyToUpgrade": "Prêt·e à Passer à la Version Supérieure ?",
"interestedLearningMore": "Vous souhaitez en Apprendre Plus ?",
"checkGroupPlanFAQ": "Allez voir la <a href='/static/faq#what-is-group-plan'> FAQ des Offres de Groupe</a> pour en apprendre plus sur comment profiter complètement de l'expériences des tâches partagées.",
"messageCopiedToClipboard": "Message copié dans le presse-papier."
"checkGroupPlanFAQ": "Allez voir la <a href='/static/faq#what-is-group-plan'> FAQ des Offres de Groupe</a> pour en apprendre plus sur comment profiter complètement de l'expériences des tâches partagées."
}

View File

@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Cet objet ne peut être acheté en quantités supérieures à 1.",
"notificationsRequired": "Les identifiants de notification sont requis.",
"unallocatedStatsPoints": "Vous avez <span class=\"notification-bold-blue\"><%= points %> points d'attribut non alloués</span>",
"beginningOfConversation": "Ceci marque le commencement de votre conversation avec <%= userName %>.",
"messageDeletedUser": "Désolé, ce compte a été supprimé.",
"messageMissingDisplayName": "Il manque le pseudo.",
"reportedMessage": "Vous avez signalé ce message à l'équipe de modération.",
"canDeleteNow": "Vous pouvez maintenant supprimer ce message si vous le souhaitez.",
"beginningOfConversationReminder": "N'oubliez pas de communiquer avec politesse et respect, tout en suivant les règles de vie en communauté !",
"newsPostNotFound": "Message de nouveautés non trouvé ou vous n'y avez pas accès.",
"messageAllUnEquipped": "Tout retiré.",
"messageBackgroundUnEquipped": "Arrière-plan retiré.",

View File

@@ -85,8 +85,8 @@
"mountsReleased": "Montures libérées",
"welcomeStable": "Bienvenue chez vos Familiers et Montures !",
"welcomeStableText": "Bienvenu à l'étable ! Je suis Matt, le maître des bêtes. Quand vous complétez une tâche, vous avez une chance de recevoir un œuf ou une potion d'éclosion pour faire éclore des familiers. Quand vous faites éclore un familier, il apparaîtra ici ! Cliquez sur l'image d'un familier pour l'ajouter à votre avatar. Nourrissez-les tous ici avec la nourriture que vous trouverez, et ils évolueront en de robustes montures.",
"petLikeToEat": "Que préfère manger mon Familier ?",
"petLikeToEatText": "Vos Familiers grandiront quelle que soit la nourriture que vous leur donnerez, mais il·elle·s grandiront plus rapidement s'ils mangent l'Aliment pour Familier qu'il·elle·s préfèrent. Faites des essais pour découvrir les goûts de chacun·e, ou consultez les réponses ici : <br/> <a href=\"https://habitica.fandom.com/wiki/Food_Preference/static/faq#pet-foods\" target=\"_blank\">https://habitica.fandom.com/wiki/Food_Preferencecom/static/faq#pet-foods</a>",
"petLikeToEat": "Que préfère manger mon familier ?",
"petLikeToEatText": "Les familiers grandissent, peu importe de quoi vous les nourrissez, mais ils grandiront plus vite si vous leur donnez ce qu'ils préfèrent. Expérimentez et trouvez par vous-même, ou regardez les réponses ici : <br/> <a href=\"https://habitica.fandom.com/fr/wiki/Préférences_alimentaires\" target=\"_blank\">https://habitica.fandom.com/fr/wiki/Préférences_alimentaires</a>",
"filterByStandard": "Standards",
"filterByMagicPotion": "Potions magiques",
"filterByQuest": "Quêtes",

View File

@@ -2,7 +2,7 @@
"achievement": "הישג",
"onwards": "הלאה!",
"levelup": "בזכות ביצוע משימות בחיים האמיתיים, עלית רמה והדמות שלך נרפאה לחלוטין!",
"reachedLevel": "הגעת לרמה <%= level %>",
"reachedLevel": "הגעת לשלב <%= level %>",
"achievementLostMasterclasser": "משלים ההרפתקאות: סדרת הרב-אמן",
"achievementLostMasterclasserText": "השלימו את כל שש-עשר המשימות בסדרת הרפתקאות של הרב-אמן ופתרו את תעלומת הרב-אמן האבוד!",
"viewAchievements": "הצגת ההישגים",
@@ -153,17 +153,5 @@
"achievementDinosaurDynastyModalText": "אספת את כל חיות המחמד של הציפורים והדינוזאורים!",
"achievementBonelessBoss": "מנהל חסר-עצמות",
"achievementBonelessBossText": "בקע את כל הצבעים הרגילים של חיות ים: חיפושית, פרפר, דיונון, חשופית ים, תמנון, חילזון, ועכביש!",
"achievementBonelessBossModalText": "אספת את כל החיות חסרות עמוד השדרה!",
"achievementRodentRuler": "שליט.ת המכרסמים",
"achievementRodentRulerText": "בקעת את כל הצבעים הסטנדרטיים של מכרסמי מחמד: שרקן, חולדה וסנאי!",
"achievementRodentRulerModalText": "אספת את כל מכרסמי המחמד!",
"achievementCats": "רועה.ת חתולים",
"achievementCatsModalText": "אספת את כל חתולי המחמד!",
"achievementDuneBuddyModalText": "אספת את כל חיות המדבר!",
"achievementRoughRider": "רוכב מחוספס",
"achievementRoughRiderText": "בקעת את כל הצבעים הבסיסיים של חיות המחמד והמרכבים הלא נוחים: קקטוס, קיפוד ואבן!",
"achievementCatsText": "בקעת את כל הצבעים הסטנדרטיים של חתולי המחמד: צ׳יטה, אריה, טיגריס שנחרבי ונמר!",
"achievementRoughRiderModalText": "אספת את כל צבעי הבסיס של החיות מחמד והמרכבים הלא נוחים!",
"achievementDuneBuddy": "ידיד הדיונות",
"achievementDuneBuddyText": "הבקיע את כל חיות המדבר בצבעים סטנדרטיים: ארמדיל, קקטוס, שועל, צפרדע, נחש ועכביש!"
"achievementBonelessBossModalText": "אספת את כל החיות חסרות עמוד השדרה!"
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "אנא עזרו למנחים שלנו ותסבירו למה אתם מדווחים על המודעה הזו כהפרה, למשל ספאם, קללות, נושא דתי, קנאות, השמצות, נושאים למבוגרים בלבד, אלימות.",
"optional": "רשות",
"needsTextPlaceholder": "הקלד את ההודעה שלך כאן.",
"copyMessageAsToDo": "העתקת ההודעה בתור משימה לביצוע",
"copyAsTodo": "העתקה בתור משימה לביצוע",
"messageAddedAsToDo": "ההודעה הועתקה בתור משימה לביצוע.",
"leaderOnlyChallenges": "רק מנהיג חבורה יכול לייצר אתגרים",
"sendGift": "הענקת מתנה",
"inviteFriends": "הזמנת חברים",

View File

@@ -46,6 +46,7 @@
"messageNotAbleToBuyInBulk": "",
"notificationsRequired": "",
"unallocatedStatsPoints": "",
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>. Remember to be kind, respectful, and follow the Community Guidelines!",
"messageDeletedUser": "המשתמש הזה מחק את חשבונו, עמך הסליחה.",
"messageMissingDisplayName": "חסר שם תצוגה.",
"reportedMessage": "דיווחת על ההודעה הזו למנהלים.",

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Molimo te da pomogneš našim moderatorima tako da im daš do znanja zbog kakvog prekršaja prijavljuješ ovu objavu, npr. spam, psovanje, religijske kletve, netrpeljivost, pogrde, teme za odrasle, nasilje.",
"optional": "Neobavezno",
"needsTextPlaceholder": "Napiši svoju poruku ovdje.",
"copyMessageAsToDo": "Kopiraj poruku kao Zadatak",
"copyAsTodo": "Kopiraj kao Zadatak",
"messageAddedAsToDo": "Poruka je kopirana kao Zadatak.",
"leaderOnlyChallenges": "Samo vođa grupe može stvarati izazove",
"sendGift": "Pošalji poklon",
"inviteFriends": "Pozovi prijatelje",

View File

@@ -46,6 +46,7 @@
"messageNotAbleToBuyInBulk": "Nije moguće kupiti više od 1 komada ovog artikla.",
"notificationsRequired": "Potrebni se ID-evi obavijesti.",
"unallocatedStatsPoints": "Imaš <span class=\"notification-bold-blue\"><%= points %> neraspodijeljenih Statističkih bodova</span>",
"beginningOfConversation": "Ovo je početak tvog razgovora s <%= userName %>. Ne zaboravi iskazati ljubaznost, poštivanje i poštuj Smjernice za zajednicu!",
"messageDeletedUser": "Žao nam je, ovaj korisnik je izbrisao svoj račun.",
"messageMissingDisplayName": "Nedostaje ime za prikazivanje."
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Kérlek segíts moderátorainkat azzal hogy tudomásunkra hozod miért jelented ezt a bejegyzést, pl: spam, káromkodás, vallásos kijelentés, becsmérlő kifejezés, felnőtt tartalom, erőszak.",
"optional": "Opcionális",
"needsTextPlaceholder": "Ide írd az üzeneted.",
"copyMessageAsToDo": "Üzenet másolása tennivalóként",
"copyAsTodo": "Másolás tennivalóként",
"messageAddedAsToDo": "Üzenet másolva tennivalóként.",
"leaderOnlyChallenges": "Csak csapatvezetők hozhatnak létre kihívásokat",
"sendGift": "Ajándék küldése",
"inviteFriends": "Barátok meghívása",

View File

@@ -46,11 +46,13 @@
"messageNotAbleToBuyInBulk": "Ez a tárgy nem vásárolható meg 1-nél többször.",
"notificationsRequired": "Értesítés azonosítók szükségesek.",
"unallocatedStatsPoints": "Van <span class=\"notification-bold-blue\"><%= points %> kiosztatlan tulajdonság pontod</span>",
"beginningOfConversation": "Elkezdtél beszélgetni <%= userName %> felhasználóval.",
"messageDeletedUser": "Ez a felhasználó törölte a fiókját.",
"messageMissingDisplayName": "Hiányzó nyilvános név.",
"newsPostNotFound": "Új üzenet nem található vagy nincsen hozzá hozzáférésed.",
"canDeleteNow": "Ha szeretnéd mostmár törölheted az üzenetet.",
"reportedMessage": "Bejelentetted ezt az üzenetet a moderátoroknak.",
"beginningOfConversationReminder": "Legyél kedves és tisztelettudó, és kövesd a közösségi irányelveket!",
"messageAllUnEquipped": "Minden eltávolítva.",
"messageBackgroundUnEquipped": "Háttér eltávolítva.",
"messagePetMountUnEquipped": "Háziállat és hátas eltávolítva.",

View File

@@ -833,27 +833,5 @@
"backgroundMonstrousCaveNotes": "Tataplah perut Gua Mengerikan.",
"backgroundJackOLanternStacksText": "Tumpukan Lentera Labu",
"backgroundJackOLanternStacksNotes": "Kagumi sudut Tumpukan Lentera Labu.",
"backgroundWinterMountainRangeText": "Barisan Gunung Musim Dingin",
"backgrounds032024": "SET 118 : Dirilis pada Maret 2024",
"backgroundFloweringForestText": "Hutan Berbunga",
"backgroundFloweringForestNotes": "Menikmati suasana di Hutan Berbunga.",
"backgroundRainyRainforestText": "Hutan Hujan",
"backgroundRainyRainforestNotes": "Menikmati suasana menyegarkan di Hutan Hujan.",
"backgroundDogParkText": "Taman Anjing",
"backgroundDogParkNotes": "Bersenda gurau di Taman Anjing.",
"backgrounds022024": "SET 117 : Dirilis pada Februari 2024",
"backgroundColorfulStreetText": "Jalan Warna Warni",
"backgroundColorfulStreetNotes": "Menikmati Jalan Warna Warni.",
"backgroundSwanBoatText": "Perahu Angsa",
"backgroundSwanBoatNotes": "Menaiki Perahu Angsa.",
"backgroundHeartTreeTunnelText": "Terowongan Pohon Hati",
"backgrounds042024": "SET 119 : Dirilis pada April 2024",
"backgroundForestSunsetText": "Matahari terbenam di hutan",
"backgroundForestSunsetNotes": "Berjemur dibawah cahaya matahari terbenam di hutan.",
"backgroundWallFloweringVinesText": "Pagar dengan Tanaman Merambat",
"backgroundFrozenBluePondNotes": "Bersantai di Kolam Biru Beku.",
"backgroundIceBubbleLakeText": "Danau Gelembung Es",
"backgroundFrozenBluePondText": "Kolam Biru Beku",
"backgroundIceBubbleLakeNotes": "Berdiri dengan hati-hati di atas Danau Gelembung Es.",
"backgroundHeartTreeTunnelNotes": "Mengendara melewati Terowongan Pohon Hati."
"backgroundWinterMountainRangeText": "Barisan Gunung Musim Dingin"
}

View File

@@ -13,7 +13,7 @@
"commGuideList02D": "<strong>Berhati-hatilah karena pengguna Habitica dari berbagai usia dan latar belakang</strong>. Tantangan dan profil pemain tidak boleh mengandung hal dewasa, kata kotor, atau mempromosikan perselisihan dan konflik.",
"commGuideList02E": "<strong>Jika moderator atau staf memberi tahu kamu bahwa sebuah istilah tidak dibolehkan di Habitica, bahkan jika istilah tersebut menurut kamu tidak masalah, maka keputusan larangan itu adalah final</strong>. Selebihnya, hinaan akan diberi sanksi berat, sebab merupakan pelanggaran terhadap Persyaratan Layanan.",
"commGuideList02G": "<strong>Patuhi segera segala permintaan Staf.</strong> Ini juga termasuk, tetapi tidak terbatas pada, memintamu untuk membatasi jumlah postinganmu di ruang tertentu, menghapus konten yang tidak sesuai dari profilmu, dll. Jangan berdebat dengan Staf. Jika kamu mempunyai masalah atau pendapat terkait sikap Staf, kirimkan email pada <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>untuk ditindaklanjuti oleh manager komunitas kami.",
"commGuideList02J": "<strong>Jangan mengirim spam</strong>. Spam dapat berupa: mengirimkan beberapa pesan pribadi tanpa izin, mengirimkan pesan yang tidak logis atau tidak bermakna, mengirimkan banyak pesan promosi tentang Party atau Tantangan, atau membuat beberapa Tantangan yang serupa atau berkualitas rendah secara beruntun. Staff memiliki keleluasaan untuk menentukan apakah pesan dianggap sebagai spam.",
"commGuideList02J": "<strong>Jangan mengirim spam</strong>. Spam termasuk, tapi tidak terbatas pada: mengirimkan komentar yang sama di banyak tempat berbeda, <strong>mengirimkan tautan tanpa penjelasan atau konteks</strong>, mengirimkan pesan tidak masuk akal, mengirimkan pesan promosi yang banyak tentang sebuah Guild, Party, atau Tantangan, atau mengirimkan banyak pesan berturut-turut. Jika seseorang meng-klik sebuah tautan yang akan memberikan keuntungan untuk kamu, kamu harus memberitahukan hal tersebut di pesan, kalau tidak demikian maka dianggap sebagai spam. Moderator dapat menentukan yang mana spam atau bukan berdasarkan penilaian mereka.",
"commGuideList02K": "<strong>Hindarilah mengirim tulisan dengan header besar di ruang obrolan publik, khususnya di Kedai Minuman</strong>. Sama seperti HURUF KAPITAL SELURUHNYA, hal itu dibaca sebagai kamu berteriak, dan mengganggu suasana obrolan yang nyaman.",
"commGuideList02L": "<strong>Kami sangat tidak menyarankan pertukaran informasi pribadi -- khususnya informasi yang dapat digunakan untuk mengidentifikasimu -- di ruang obrolan publik</strong>. Informasi mengidentifikasi dapat termasuk, namun tidak terbatas pada: alamat kamu, alamat email kamu, dan token API/passwordmu. Ini untuk keamananmu! Staf ataupun moderator bisa menghapus postingan sesuai kebijakan mereka. Jika kamu diminta untuk memberi informasi pribadi di dalam sebuah Guild privat, Party, atau PM, kami sangat menyarankan kamu menolak dengan sopan kemudian memberitahu staf dan moderator dengan cara 1) melaporkan pesan 2) mengirim pesan ke <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> dan menyertakan tangkapan layar.",
"commGuidePara037": "<strong>Tidak boleh ada Party atau Grup yang dibuat dengan tujuan menyerang kelompok atau individu mana pun</strong>. Lawan kebiasaan buruk, bukan sesama petualang!",

View File

@@ -72,7 +72,7 @@
"faqQuestion35": "Saya memberi makan hewan peliharaan saya dan hewan itu menghilang! Apa yang telah terjadi?",
"webFaqAnswer35": "Setelah kamu memberi makan hewan peliharaanmu dalam jumlah yang cukup untuk mengangkatnya menjadi tunggangan, kamu harus menetaskan hewan peliharaan jenis itu lagi agar bisa dimasukkan ke dalam kandangmu.\n\nUntuk melihat Mount di aplikasi seluler:\n\n* Dari Menu, pilih “Hewan Peliharaan & Tunggangan” dan beralih ke tab Tunggangan\n\nUntuk melihat Mount di situs web:\n\n* Dari menu Inventaris, pilih “Stabil” dan gulir ke bawah ke bagian Mount",
"faqQuestion36": "Bagaimana cara mengubah tampilan Avatar saya?",
"webFaqAnswer36": "Ada banyak cara untuk mengkostumisasi Avatar Habitica-mu! Kamu bisa mengganti bentuk tubuh, warna dan gaya rambut, warna kulit, atau kacamata dan alat bantu mobilisasi dengan memilih \"Ubah Avatar\" dari menu.\n\nUntuk kostumisasi Avatar dari aplikasi seluler:\n* Dari Menu, pilih “Ubah Avatar\n\nUntuk kostumisasi Avatar dari website:\n* dari menu User pada navigasi, pilih \"Ubah Avatar\"",
"webFaqAnswer36": "Ada banyak cara untuk menyesuaikan tampilan Avatar Habitica Anda! Anda dapat mengubah bentuk tubuh, gaya dan warna rambut, warna kulit, atau menambahkan kacamata atau alat bantu mobilitas dengan memilih Kustomisasi (atau Edit) Avatar dari menu.\n\nUntuk menyesuaikan Avatar Anda di aplikasi seluler:\n\n* Dari menu, pilih “Kustomisasi Avatar\n\nUntuk menyesuaikan Avatar Anda di situs web:\n\n* Dari menu pengguna di navigasi, pilih \"Edit Avatar\"",
"contentReleaseChanges": "Perubahan Rilis Konten",
"contentFaqPara1": "Ingin tahu lebih lanjut? Baca terus di bawah ini!",
"contentQuestion0": "Apa yang berubah?",
@@ -126,9 +126,9 @@
"contentFaqPara3": "Jika Anda memiliki pertanyaan yang tidak tercakup dalam jawaban di atas, Anda selalu dapat menghubungi tim kami di <%= mailto %>! Kami sangat antusias dengan jadwal rilis konten baru ini dan menantikan lebih banyak proyek di masa mendatang untuk membantu menjadikan Habitica lebih baik bagi semua pemain.",
"webFaqAnswer41": "Mystic Hourglasses adalah mata uang eksklusif Pelanggan Habitica yang digunakan di Toko Time Travelers! Hourglass dikirimkan pada jadwal yang ditetapkan berdasarkan paket langganan Anda.\n\nJadwal pengiriman Hourglass:\n* Pelanggan 1 Bulan mendapatkan 1 Hourglass di awal bulan setelah pembayaran ke-3 berturut-turut.\n* Pelanggan 3 Bulan mendapatkan 1 Hourglass segera setelah berlangganan, lalu 1 Hourglass lagi di awal bulan setelah setiap pembaruan.\n* Pelanggan 6 Bulan mendapatkan 2 Hourglass segera setelah berlangganan, lalu 2 Hourglass lagi di awal bulan setelah setiap pembaruan.\n* Pelanggan 12 Bulan mendapatkan 4 Hourglass segera setelah berlangganan, lalu 4 Hourglass lagi di awal bulan setelah setiap pembaruan.",
"faqQuestion42": "Apa yang dapat saya lakukan untuk meningkatkan akuntabilitas?",
"webFaqAnswer42": "Salah satu cara terbaik untuk memotivasi diri dan jadi lebih bertanggung jawab untuk menyelesaikan tugas adalah dengan bergabung dalam Party! Menyelesaikan Misi untuk dapatkan Hewan Peliharaan dan Peralatan lebih seru bersama pemain Habitica lainnya! Kamu bisa terima buff dari Keterampilan anggota Party dan juga meningkatkan motivasi.\n\nCara lain untuk meningkatkan akuntabilitas adalah dengan bergabung dalam Tantangan. Tugas baru yang berhubungan dengan tujuan tertentu akan otomatis ditambahkan saat mengikuti Tantangan. Elemen kompetisi melawan pemain Habitica lainnya bisa memberimu motivasi saat berjuang untuk mendapatkan hadiah Permata. Ada Tantangan resmi yang dibuat oleh Tim Habitica dan juga Tantangan yang dibuat oleh pemain lain.",
"webFaqAnswer42": "Salah satu cara terbaik untuk memotivasi diri sendiri dan membuat diri Anda bertanggung jawab untuk menyelesaikan tugas adalah dengan bergabung dalam Pesta! Berpesta dengan pemain Habitica lainnya adalah cara yang bagus untuk menyelesaikan Misi untuk menerima Hewan Peliharaan dan Peralatan, menerima buff dari Keterampilan anggota Pesta, dan meningkatkan motivasi Anda.\n\nCara lain untuk meningkatkan akuntabilitas adalah dengan bergabung dalam Tantangan. Tantangan secara otomatis menambahkan tugas yang terkait dengan tujuan tertentu ke daftar Anda! Tantangan juga menambahkan elemen kompetisi melawan pemain Habitica lainnya yang dapat memberi Anda dorongan motivasi saat Anda berjuang untuk mendapatkan hadiah Permata. Ada Tantangan resmi yang dibuat oleh Tim Habitica dan juga Tantangan yang dibuat oleh pemain lain.",
"faqQuestion43": "Bagaimana cara saya mengikuti Quest?",
"webFaqAnswer43": "Untuk memulai Quest, kamu harus menjadi anggota Party. Kamu bisa menjadi member solo di Party dan melakukan Quest sendirian, atau mengundang pemain Habitica lainnya untuk menaklukan Quest dengan lebih cepat!\n\nPilih Gulungan Quest dari inventarismu dengan memilih tombol \"Mulai Quest\" dari Party-mu. Selesaikan tugasmu seperti biasa untuk melanjutkan Quest! Tugas yang kamu lakukan akan memberikan serangan kepada moster jika memilih Boss Quest, atau memberikan kesempatan untuk menemykan item jika memilih Collection Quest. Semua progres yang terkumpul akan diterapkan pada hari berikutnya.\n\nJika kamu sudah menghasilkan serangan yang cukup untuk mengalahkan Boss atau mengumpulkan semua item, Questmu selesai. Kamu akan menerima hadiah!",
"webFaqAnswer43": "Untuk memulai Quest, Anda harus menjadi anggota Party. Party dapat berupa petualangan solo di mana Anda menantang Quest sendirian, atau Anda dapat mengundang pemain Habitica lainnya untuk menyelesaikan Quest dengan lebih cepat!\n\nPilih Quest Scroll dari inventaris Anda dengan memilih tombol \"Begin Quest\" dari Party Anda. Selesaikan tugas Anda seperti biasa untuk melanjutkan Quest! Anda akan membangun damage terhadap monster jika Anda mengambil Boss Quest, atau memiliki kesempatan untuk menemukan item jika Anda mengambil Collection Quest. Semua kemajuan yang tertunda diterapkan pada hari berikutnya.\n\nSaat Anda menghasilkan damage yang cukup atau mengumpulkan semua item, Quest selesai dan Anda akan menerima hadiah!",
"faqQuestion44": "Bagaimana saya dapat menghapus tugas Tantangan?",
"webFaqAnswer44": "Anda harus meninggalkan Tantangan atau menunggu Tantangan ditutup untuk menghapus tugas terkait. Ikon megafon merah menunjukkan Tantangan telah ditutup dan megafon abu-abu menunjukkan Tantangan masih berjalan.\n\nUntuk menghapus tugas Tantangan di aplikasi **Android**:\n\n1. Ketuk tugas yang termasuk dalam Tantangan. 2. Ketuk \"Hapus\" di sudut kanan atas layar.\n\n3. Pilih untuk menghapus tugas Tantangan dari daftar tugas Anda.\n\nUntuk menghapus tugas Tantangan di aplikasi **iOS**:\n\n1. Temukan tugas Tantangan yang ingin Anda hapus dan lihat ikon megafon.\n\n2. Jika ikon megafon berwarna merah, ketuk tugas dan pilih \"Hapus\" di bagian bawah\n\n3. Jika ikon megafon berwarna abu-abu, Anda harus menemukan Tantangan dan meninggalkannya untuk menghapus tugas.\n\nUntuk menghapus tugas Tantangan di **situs web**:\n\n1. Temukan tugas Tantangan yang ingin Anda hapus dan lihat ikon megafon. 2. Jika ikon megafon berwarna merah, klik ikon tersebut lalu pilih untuk menghapus tugas dari daftar tugas Anda.\n\nJika ikon megafon berwarna abu-abu, Anda harus menemukan Tantangan dan meninggalkannya untuk menghapus tugas.",
"faqQuestion45": "Avatar saya berubah menjadi manusia salju, bintang laut, bunga, atau hantu. Bagaimana cara mengubahnya kembali?",
@@ -159,7 +159,7 @@
"faqQuestion52": "Bisakah saya berhenti mencari Partai?",
"webFaqAnswer52": "Jika Anda tidak ingin lagi mencari Party, Anda dapat berhenti mencari kapan saja.\n\nUntuk berhenti mencari Party di aplikasi seluler:\n* Dari menu, pilih \"Party\" dan ketuk \"Leave\" di bagian bawah layar.\n\nUntuk berhenti mencari Party di situs web Habitica:\n* Pilih \"Party\" dari navigasi dan klik \"Leave\" di pop-up.",
"faqQuestion53": "Saya punya Pesta, bagaimana cara mencari anggota lainnya?",
"webFaqAnswer53": "Jika kamu menggunakan situs web Habitica, pilih \"Temukan Anggota\" dari menu dropdown Party. Jika kamu menggunakan aplikasi Android, ketuk \"Temukan Anggota\" di atas daftar anggota Party-mu. Daftar pemain yang sedang aktif mencari Party akan muncul dan dapat diundang untuk bergabung.\n\nUntuk membantu menemukan yang anggota cocok untuk Party-mu, ada beberapa informasi, seperti bahasa, kelas, level, dan berapa hari mereka telah menggunakan Habitica. Jika ingin mengobrol dengan seseorang sebelum mengirim undangan, kamu bisa melihat Profil mereka dan mengirim pesan.",
"webFaqAnswer53": "Jika Anda menggunakan situs web Habitica, pilih \"Temukan Anggota\" dari menu tarik-turun Grup. Jika Anda menggunakan aplikasi Android, ketuk \"Temukan Anggota\" di atas daftar anggota Grup Anda. Ini akan menampilkan daftar pemain yang secara aktif mencari Grup dan dapat diundang untuk bergabung.\n\nUntuk membantu menemukan yang cocok untuk Grup Anda, Anda akan melihat beberapa informasi, seperti bahasa, kelas, level, dan berapa hari mereka telah menggunakan Habitica. Jika Anda ingin mengobrol dengan seseorang sebelum mengirim undangan, Anda dapat melihat Profil mereka dan mengirim pesan.",
"faqQuestion54": "Berapa banyak anggota yang dapat saya undang ke Pesta saya?",
"webFaqAnswer54": "Setiap grup memiliki batas maksimum 30 anggota dan minimum 1 anggota. Undangan yang tertunda dihitung dalam hitungan anggota. Misalnya, 29 anggota dan 1 undangan yang tertunda akan dihitung sebagai 30 anggota. Untuk menghapus undangan yang tertunda, pemain yang diundang harus menerima atau menolak, atau pemimpin grup harus membatalkan undangan.",
"faqQuestion55": "Bisakah saya mengundang seseorang yang sudah saya kenal?",
@@ -186,7 +186,5 @@
"faqQuestion65": "Apakah Tugas Bersama didukung di aplikasi ponsel?",
"webFaqAnswer65": "Meskipun aplikasi seluler belum sepenuhnya mendukung semua fungsi Paket Grup, Anda tetap dapat menyelesaikan tugas bersama dari aplikasi iOS dan Android!\n\nDi Android, Anda dapat mengetuk Nama Tampilan di bagian atas layar saat melihat tugas untuk beralih ke papan tugas bersama. Dari sana, Anda dapat melihat anggota, mengakses obrolan, dan membuat, menyelesaikan, atau menetapkan tugas.\n\nAnda juga dapat mengaktifkan preferensi untuk menyalin tugas bersama ke papan tugas pribadi sehingga Anda dapat menyelesaikan semua tugas dari satu tempat.\n\nUntuk melakukannya di aplikasi seluler:\n\n* Buka Pengaturan dan aktifkan “Salin tugas bersama”\n\nUntuk melakukannya di situs web Habitica:\n\n* Navigasi ke Paket Grup dan aktifkan tombol “Salin tugas” di papan tugas bersama",
"faqQuestion66": "Apa perbedaan antara tugas bersama dari Rencana Grup dan tugas Tantangan?",
"webFaqAnswer66": "Papan tugas bersama dari Rencana Grup lebih dinamis dibnading dengan Tantangan, dimana secara berkala konten dari papan tersebut diperbarui. Tantangan justru lebih baik jika terdapat sederet tugas lalu dibagikan ke banyak orang.\n\nRencana Grup juga punya fasilitas berbayar, sementara Tantangan tersedia secara gratis bagi setiap orang.\n\nAnda tidak bisa mengambil tugas tertentu dalam Tantangan, dan Tantangan pun tidak punya pengulangan waktu bersama. Umumnya, Tantangan menawarkan kendali yang lebih sederhana dan interaksi secara langsung.",
"subscriptionBenefitsAdjustments": "Penyesuaian Keuntungan Pelanggan",
"subscriptionBenefitsFaqTitle": "Penyesuaian Keuntungan Pelanggan FAQ"
"webFaqAnswer66": "Papan tugas bersama dari Rencana Grup lebih dinamis dibnading dengan Tantangan, dimana secara berkala konten dari papan tersebut diperbarui. Tantangan justru lebih baik jika terdapat sederet tugas lalu dibagikan ke banyak orang.\n\nRencana Grup juga punya fasilitas berbayar, sementara Tantangan tersedia secara gratis bagi setiap orang.\n\nAnda tidak bisa mengambil tugas tertentu dalam Tantangan, dan Tantangan pun tidak punya pengulangan waktu bersama. Umumnya, Tantangan menawarkan kendali yang lebih sederhana dan interaksi secara langsung."
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Tolong bantu moderator kami dengan memberi tahu kami mengapa kamu melaporkan postingan ini karena pelanggaran, mis., spam, sumpah serapah, sumpah agama, kefanatikan, penghinaan, topik dewasa, kekerasan.",
"optional": "Tidak wajib",
"needsTextPlaceholder": "Ketikkan pesan.",
"copyMessageAsToDo": "Salin pesan sebagai To Do",
"copyAsTodo": "Salin sebagai To Do",
"messageAddedAsToDo": "Pesan tersalin sebagai To Do.",
"leaderOnlyChallenges": "Hanya ketua grup yang dapat membuat tantangan baru",
"sendGift": "Kirim Hadiah",
"inviteFriends": "Undang Teman",

View File

@@ -46,6 +46,7 @@
"messageNotAbleToBuyInBulk": "Item ini tidak dapat dibeli dalam jumlah lebih dari 1.",
"notificationsRequired": "Id notifikasi diperlukan.",
"unallocatedStatsPoints": "Kamu punya <span class=\"notification-bold-blue\"><%= points %>Poin Atribut yang belum teralokasi</span>",
"beginningOfConversation": "Ini permulaan percakapanmu dengan <%= userName %>.",
"messageDeletedUser": "Maaf ya, pengguna ini sudah menghapus akunnya.",
"messageMissingDisplayName": "Nama tampilan tidak ada.",
"canDeleteNow": "Sekarang kamu dapat menghapus pesan.",
@@ -54,6 +55,7 @@
"messagePetMountUnEquipped": "Peliharaan dan Tunggangan dilepaskan.",
"messageBackgroundUnEquipped": "Latar Belakang dilepaskan.",
"messageAllUnEquipped": "Semuanya dilepaskan.",
"beginningOfConversationReminder": "Ingatlah untuk sopan, menghargai, dan mengikuti Pedoman Komunitas!",
"reportedMessage": "Kamu telah melaporkan pesan ini ke moderator.",
"newsPostNotFound": "Tulisan Berita tidak ditemukan atau kamu tidak punya akses."
}

View File

@@ -95,6 +95,9 @@
"whyReportingPostPlaceholder": "Per favore aiuta i nostri moderatori facendoci sapere perché stai segnalando questo post per una infrazione, es. spam, imprecazioni, bestemmie, bigottismo, argomenti per adulti, violenza.",
"optional": "Opzionale",
"needsTextPlaceholder": "Scrivi il tuo messaggio qui.",
"copyMessageAsToDo": "Copia messaggio come Cosa da Fare",
"copyAsTodo": "Copia come Cosa da Fare",
"messageAddedAsToDo": "Messaggio copiato come Cosa da Fare.",
"leaderOnlyChallenges": "Solo il leader del gruppo può creare le sfide",
"sendGift": "Invia un regalo",
"inviteFriends": "Invita amici",

View File

@@ -46,10 +46,12 @@
"messageNotAbleToBuyInBulk": "Non puoi comprare più di 1 unità di questo oggetto.",
"notificationsRequired": "Sono necessari gli id delle notifiche.",
"unallocatedStatsPoints": "Hai <span class=\"notification-bold-blue\"><%= points %> Punti Statistica non allocati</span>",
"beginningOfConversation": "Stai iniziando una conversazione con <%= userName %>.",
"messageDeletedUser": "Siamo spiacenti, questo utente ha eliminato il suo account.",
"messageMissingDisplayName": "Nome Pubblico mancante.",
"canDeleteNow": "Ora puoi eliminare il messaggio se lo desideri.",
"reportedMessage": "Hai segnalato questo messaggio ai moderatori.",
"beginningOfConversationReminder": "Ricorda di scrivere con gentilezza e rispetto, seguendo le Linee guida della community!",
"newsPostNotFound": "Notizia non trovata oppure non hai i permessi per vederla.",
"messageAllUnEquipped": "Rimosso tutto.",
"messageBackgroundUnEquipped": "Sfondo rimosso.",

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