diff --git a/common/locales/cs/tasks.json b/common/locales/cs/tasks.json index 512c5e23da..d61fb9fe44 100644 --- a/common/locales/cs/tasks.json +++ b/common/locales/cs/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Posilňující lektvar", "fortifyPop": "Obnoví všechny úkoly do neutrální (žluté) barvy a obnoví ztracené zdraví.", "fortify": "Posilnění", - "fortifyText": "Posilnění vrátí všechny tvé úkoly do neutrálního (žlutého) stavu, ve kterém byly v okamžitku přidání, a doplní zdraví na maximum. Považuj to za poslední možnou variantu. Červené úkoly poskytují dobrou motivaci ke zlepšení. Ale pokud tě všechna ta červená naplňuje zoufalstvím a začátek každého dne se ukáže jako smrtící, investuj drahokamy a oddechni si.", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Určitě chceš odstranit tento úkol?", "streakCoins": "Bonus za šňůru!", "pushTaskToTop": "Přesunout úkol na vrchol", diff --git a/common/locales/da/tasks.json b/common/locales/da/tasks.json index ae8463cf58..53fd80e8b9 100644 --- a/common/locales/da/tasks.json +++ b/common/locales/da/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Forstærkningseliksir", "fortifyPop": "Returnerer alle opgaver til neutral værdi (gul farve) og giver alt tabt helbred tilbage.", "fortify": "Forstærk", - "fortifyText": "Forstærk vil returnere alle dine opgaver til en neutral (gul) værdi, som om du lige har tilføjet dem, og give dig fuldt Helbred tilbage. Tænk på dette som en sidste mulighed! Røde opgaver tilskynder dig til at forbedre dig. Men hvis al den røde farve gør dig trist, og en ny dag er dødbringende, brug ædelstenene og bliv benådet!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Er du sikker på, at du vil slette denne opgave?", "streakCoins": "Stribebonus!", "pushTaskToTop": "Flyt opgave til toppen", diff --git a/common/locales/de/front.json b/common/locales/de/front.json index 4d2ccf67dc..fd80be160c 100644 --- a/common/locales/de/front.json +++ b/common/locales/de/front.json @@ -2,7 +2,7 @@ "FAQ": "FAQ", "accept1Terms": "Durch Klicken auf diesen Knopf, stimme ich den", "accept2Terms": "zu, sowie der", - "alexandraQuote": "Couldn't NOT talk about [Habitica] during my speech in Madrid. Must-have tool for freelancers who still need a boss.", + "alexandraQuote": "Ich konnte während meiner Rede in Madrid nicht NICHT über [Habitica] reden. Ein absolutes Must-have Hilfsmittel für Freiberufler, die trotzdem einen Chef brauchen.", "althaireQuote": "Kontinuierlich eine Quest zu haben, motiviert mich dazu alle meine täglichen und einmaligen Aufgaben zu erledigen. Meine größte Motivation ist, meine Gruppe nicht im Stich zu lassen.", "andeeliaoQuote": "Großartiges Produkt, ich habe gerade erst vor einigen Tagen angefangen und gehe jetzt schon bewusster und produktiver mit meiner Zeit um!", "autumnesquirrelQuote": "Ich schiebe bei der Arbeit und im Haushalt weniger auf und zahle meine Rechnungen rechtzeitig. ", @@ -38,7 +38,7 @@ "elmiQuote": "Jeden Morgen freue ich mich aufzustehen und etwas Gold zu verdienen!", "email": "E-Mail", "emailNewPass": "E-Mail neues Passwort", - "evagantzQuote": "My very first dentist appointment where the hygienist was actually excited about my flossing habits. Thanks [Habitica]!", + "evagantzQuote": "Mein erster Zahnarztbesuch, bei dem die Assistentin tatsächlich begeistert über meine Zahnseide-Gewohnheiten war. Danke [Habitica]!", "examplesHeading": "Spieler benutzen Habitica, um folgendes zu organisieren...", "featureAchievementByline": "Etwas total großartiges gemacht? Erhalte ein Abzeichen und prahle damit!", "featureAchievementHeading": "Erfolgsabzeichen", @@ -55,7 +55,7 @@ "footerMobile": "Mobil", "footerSocial": "Soziales", "forgotPass": "Passwort vergessen", - "frabjabulousQuote": "[Habitica] is the reason I got a killer, high-paying job... and even more miraculous, I'm now a daily flosser!", + "frabjabulousQuote": "[Habitica] ist der Grund, warum ich einen tollen, gut bezahlten Job gefunden habe... und noch erstaunlicher ist, dass ich jetzt täglich Zahnseide benutze!", "free": "Kostenlos spielen", "gamifyButton": "Gestalte dein Leben spielerisch!", "goalSample1": "1 Stunde Klavier üben", @@ -73,9 +73,9 @@ "history": "Verlauf", "infhQuote": "[Habitica] hat mir geholfen Struktur in mein Leben an der Universität zu bringen.", "invalidEmail": "Um das Passwort zurückzusetzen, ist eine gültige Email-Adresse notwendig.", - "irishfeet123Quote": "I've had horrible habits with clearing my place completely after meals and leaving cups all over the place. [Habitica] has cured that!", + "irishfeet123Quote": "Ich hatte die schrecklichen Angewohnheiten, nach Mahlzeiten nicht aufzuräumen und Tassen in der ganzen Wohnung stehen zu lassen. [Habitica] hat mich davon geheilt!", "joinOthers": "Schließe dich 250.000 Leuten an, die Spaß dabei haben, ihre Ziele zu erreichen!", - "kazuiQuote": "Before [Habitica], I was stuck with my thesis, as well as dissatisfied with my personal discipline regarding housework and things like learning vocabulary and studying Go theory. It turns out breaking down these tasks into smaller manageable checklists is quite the thing to keep me motivated and constantly working.", + "kazuiQuote": "Vor [Habitica] kam ich mit meiner Dissertation nicht weiter und war unzufrieden mit meiner Selbstdisziplin bei Hausarbeiten, Vokabellernen und dem Studium der Go-Theorie. Es hat sich herausgestellt, dass das Aufteilen der Aufgaben in kleinere, machbare Checklisten etwas ist, das mich motiviert und zum konstanten Arbeiten anregt.", "landingadminlink": "Verwaltungspakete", "landingend": "Noch nicht überzeugt?", "landingend2": "Hier ist eine genauere Liste", diff --git a/common/locales/de/settings.json b/common/locales/de/settings.json index 2aac51bbe3..efb06e614b 100644 --- a/common/locales/de/settings.json +++ b/common/locales/de/settings.json @@ -42,7 +42,7 @@ "customDayStart": "Tageswechsel einstellen", "24HrClock": "24h Uhr", "customDayStartInfo1": "Habitica ist so eingestellt, dass Deine täglichen Aufgaben um Mitternacht in deiner eigenen Zeitzone überprüft und zurückgesetzt werden. Wir empfehlen den folgenden Text zu lesen, bevor Du diese Einstellungen veränderst:", - "customDayStartInfo4": "Complete all your Dailies before changing the Custom Day Start or Rest in the Inn that day. Changing your Custom Day Start may cause Cron to run immediately, but after the first day it works as expected.

Allow a window of two hours for the change to take effect. For example, if it is currently set to 0 (midnight), change it before 10pm; if you want to set it to 9pm, change it before 7pm.

Enter an hour from 0 to 23 (it uses a 24 hour clock). Typing is more effective than arrow keys. Once set, reload the page to confirm that the new value is being displayed.", + "customDayStartInfo4": "Beende alle deine täglichen Aufgaben bevor du deinen persönlichen Tagesstart änderst oder checke für diesen Tag im Gasthaus ein. Das Ändern deines Tagesstarts kann bewirken, dass dein Cron sofort ausgeführt wird, aber nach dem ersten Tag funktioniert alles wie erwartet.

Plane ein Zeitfenster von zwei Stunden ein, um die Änderungen wirksam werden zu lassen. Wenn die Zeit bisher beispielsweise auf 0 (Mitternacht) gesetzt ist, ändere sie vor 22 Uhr; Wenn du sie auf 21 setzen willst, ändere sie vor 19 Uhr.

Gib eine Zahl zwischen 0 und 23 an. Tippen ist effektiver als das Benutzen der Pfeiltasten. Wenn du die Zeit gesetzt hast, lade die Seite neu, um sicherzustellen, dass der neue Wert angezeigt wird.", "misc": "Verschiedenes", "showHeader": "Header anzeigen", "changePass": "Passwort ändern", @@ -74,7 +74,7 @@ "usernameSuccess": "Dein Login Name wurde erfolgreich geändert!", "emailSuccess": "Die E-Mail-Adresse wurde erfolgreich geändert", "detachFacebook": "Facebook Registrierung löschen", - "detachedFacebook": "Successfully removed Facebook from your account", + "detachedFacebook": "Facebook erfolgreich von deinem Konto entfernt", "addedLocalAuth": "Successfully added local authentication", "data": "Daten", "exportData": "Daten exportieren", @@ -115,7 +115,7 @@ "couponText": "Manchmal verteilen wir auf Events Coupon-Codes für spezielle Ausrüstung (z.B. an unserem WonderCon-Stand)", "apply": "Anwenden", "resubscribe": "Wieder abonnieren", - "promoCode": "Promo Code", + "promoCode": "Aktionscode", "promoCodeApplied": "Promo Code aktiviert! Überprüfe dein Inventar", "promoPlaceholder": "Promo Code eingeben", "displayInviteToPartyWhenPartyIs1": "Zeige \"In Gruppe einladen\"-Schaltfläche an, wenn die Gruppe nur 1 Mitglied hat." diff --git a/common/locales/de/tasks.json b/common/locales/de/tasks.json index bd1c782a41..0b1d156ec0 100644 --- a/common/locales/de/tasks.json +++ b/common/locales/de/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Verstärkungstrank", "fortifyPop": "Setzt alle Aufgaben auf den Anfangswert (gelb) zurück und füllt Deine Lebenspunkte wieder auf.", "fortify": "Verstärken", - "fortifyText": "Verstärken wird alle Deine Aufgaben auf ihren Anfangswert (gelb) zurücksetzen, so als ob Du sie gerade erst Hinzugefügt hättest und füllt Deine Lebenspunkte wieder auf. Sieh' das als letzten Ausweg! Rote Aufgaben bieten einen guten Anreiz sich zu bessern. Aber wenn zu viel Rot Dich zur Verzweiflung treibt, und jeder Tag tödlich endet, dann investiere Deine Edelsteine hier und hol' erstmal Luft!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Bist Du sicher, dass Du diese Aufgabe löschen möchtest?", "streakCoins": "Strähnen Bonus!", "pushTaskToTop": "Aufgabe nach oben verschieben", diff --git a/common/locales/en/contrib.json b/common/locales/en/contrib.json index b84da52d98..dff045d2d0 100644 --- a/common/locales/en/contrib.json +++ b/common/locales/en/contrib.json @@ -43,7 +43,6 @@ "contributions": "Contributions", "admin": "Admin", "notGems": "is in USD, not in Gems. Aka, if this number is 1, it means 4 gems. Only use this option when manually granting gems to players, don't use it when granting contributor tiers. Contrib tiers will automatically add gems.", - "hideAds": "Hide Ads", "gamemaster": "Game Master (staff/moderator)", "backerTier": "Backer Tier", "balance": "Balance", @@ -53,11 +52,6 @@ "visitHeroes": "Visit the Hall of Heroes (contributors and backers)", "conLearn": "Learn more about contributor rewards", "conLearnHow": "Learn how to contribute to Habitica", - "removeAds": "Subscribe to remove ads", - "whyAds": "Why Ads?", - "whyAdsContent1": "Habit is an open source project, and can use all the help it can get - consider this a donation to the contributors. You also get 20 Gems from the purchase, which you can use to buy special items.", - "whyAdsContent2": "'Hey, I backed the Kickstarter!' - follow", - "whyAdsContent3": "these instructions", "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.", "surveysMultiple": "Helped Habitica grow by filling out <%= surveys %> surveys. There are no active surveys.", "currentSurvey": "Current Survey", diff --git a/common/locales/en/generic.json b/common/locales/en/generic.json index 7bf6b24daf..8f1f6f9adb 100644 --- a/common/locales/en/generic.json +++ b/common/locales/en/generic.json @@ -116,19 +116,19 @@ "greetingCard": "Greeting Card", "greetingCardExplanation": "You both receive the Cheery Chum achievement!", "greetingCardNotes": "Send a greeting card to a party member.", - "greeting0": "\"Hi there!\"", - "greeting1": "\"Just saying hello :)\"", - "greeting2": "\"`waves frantically`\"", - "greeting3": "\"Hey you!\"", + "greeting0": "Hi there!", + "greeting1": "Just saying hello :)", + "greeting2": "`waves frantically`", + "greeting3": "Hey you!", "greetingCardAchievementTitle": "Cheery Chum", "greetingCardAchievementText": "Hey! Hi! Hello! Sent or received <%= cards %> greeting cards.", "thankyouCard": "Thank-You Card", "thankyouCardExplanation": "You both receive the Greatly Grateful achievement!", "thankyouCardNotes": "Send a Thank-You card to a party member.", - "thankyou0": "\"Thank you very much!\"", - "thankyou1": "\"Thank you, thank you, thank you!\"", - "thankyou2": "\"Sending you a thousand thanks.\"", - "thankyou3": "\"I'm very grateful - thank you!\"", + "thankyou0": "Thank you very much!", + "thankyou1": "Thank you, thank you, thank you!", + "thankyou2": "Sending you a thousand thanks.", + "thankyou3": "I'm very grateful - thank you!", "thankyouCardAchievementTitle": "Greatly Grateful", "thankyouCardAchievementText": "Thanks for being thankful! Sent or received <%= cards %> Thank-You cards." } diff --git a/common/locales/en/limited.json b/common/locales/en/limited.json index 03219f0041..a69d693811 100644 --- a/common/locales/en/limited.json +++ b/common/locales/en/limited.json @@ -41,11 +41,11 @@ "seasonalItems": "Seasonal Items", "nyeCardAchievementTitle": "Auld Acquaintance", "nyeCardAchievementText": "Happy New Year! Sent or received <%= cards %> New Year's cards.", - "nye0": "\"Happy New Year! May you slay many a bad Habit.\"", - "nye1": "\"Happy New Year! May you reap many Rewards.\"", - "nye2": "\"Happy New Year! May you earn many a Perfect Day.\"", - "nye3": "\"Happy New Year! May your To-Do list stay short and sweet.\"", - "nye4": "\"Happy New Year! May you not get attacked by a raging Hippogriff.\"", + "nye0": "Happy New Year! May you slay many a bad Habit.", + "nye1": "Happy New Year! May you reap many Rewards.", + "nye2": "Happy New Year! May you earn many a Perfect Day.", + "nye3": "Happy New Year! May your To-Do list stay short and sweet.", + "nye4": "Happy New Year! May you not get attacked by a raging Hippogriff.", "holidayCard": "Received a holiday card!", "mightyBunnySet": "Mighty Bunny (Warrior)", "magicMouseSet": "Magic Mouse (Mage)", diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 0c01e4074c..c448afe75f 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -1,9 +1,7 @@ { "subscription": "Subscription", "subscriptions": "Subscriptions", - "subDescription": "Disable ads, buy gems with gold, monthly mystery item, retain progress history, double daily drop-caps, supports the devs. Click for more info.", - "disableAds": "Disable ads", - "disableAdsText": "Ads are disabled while you have an active subscription (original donors are grandfathered).", + "subDescription": "Buy gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.", "buyGemsGold": "Buy Gems with Gold", "buyGemsGoldText": "Alexander the Merchant will sell you gems at a cost of <%= gemCost %> gold per gem. His monthly shipments are initially capped at <%= gemLimit %> gems per month, but this cap increases by 5 gems for every three months of consecutive subscription, up to a maximum of 50 gems per month!", "retainHistory": "Retain full history entries", @@ -47,7 +45,6 @@ "timeSupport": "Support Hours / Month", "timeSupportText": "We will provide support for training, bugs, installation, and feature requests.", "gameFeatures": "Game features", - "gameNoAds": "Ads disabled for members", "gold2Gem": "Gems purchasable with gold", "gold2GemText": "Members will be able to purchase gems with gold, meaning none of your participants need to buy anything with real money.", "infiniteGem": "Infinite leader gems", @@ -55,7 +52,6 @@ "notYetPlan": "Plan not yet available, but click to contact us and we'll keep you updated.", "contactUs": "Contact Us", "checkout": "Checkout", - "buySubsText": "Buy gems with Gold, No Ads, Support the Devs", "sureCancelSub": "Are you sure you want to cancel your subscription?", "subCanceled": "Subscription will become inactive on", "subGemPop": "Because you subscribe to Habitica, you can purchase a number of Gems each month using Gold. You can see how many Gems are available to buy at the corner of the Gem icon.", diff --git a/common/locales/en@pirate/limited.json b/common/locales/en@pirate/limited.json index 03e0a47cdf..d708f4b7bd 100644 --- a/common/locales/en@pirate/limited.json +++ b/common/locales/en@pirate/limited.json @@ -12,10 +12,10 @@ "aquaticFriendsText": "Got splashed <%= seafoam %> times by crew members.", "valentineCard": "Valentine's Day Card", "valentineCardNotes": "Send a Valentine's Day card to a party member.", - "valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"", - "valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"", - "valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"", - "valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"", + "valentine0": "\"Roses be red\n\nMy Dailies be blue\n\nI'm happy that I'm\n\nIn a Crew wit' ye!\"", + "valentine1": "\"Roses be red\n\nViolets be nice\n\nLet's get together\n\nAn' fight against Vice!\"", + "valentine2": "\"Roses be red\n\nThis poem style be old\n\nI hope that ye like this\n\n'Cause it cost ten Gold.\"", + "valentine3": "\"Roses be red\n\nIce Drakes be blue\n\nNo treasure be better\n\nThan time spent with' ye!\"", "adoringFriends": "Adorin' Mates", "adoringFriendsText": "Avast; ye an' yer mate must really care 'bout each other! Sent er received <%= cards %> Valentine's Day cards.", "polarBear": "Polar Bear", @@ -25,7 +25,7 @@ "seasonalShop": "Seasonal Shop", "seasonalShopClosedTitle": "<%= linkStart %>Siena Leslie<%= linkEnd %>", "seasonalShopTitle": "<%= linkStart %>Seasonal Sorceress<%= linkEnd %>", - "seasonalShopClosedText": "The Seasonal Shop is currently closed!! I don't know where the Seasonal Sorceress is now, but I bet she'll be back during the next Grand Gala!", + "seasonalShopClosedText": "The Seasonal Shop currently be closed!! I don't know where th' Seasonal Sorceress be now, but I bet she'll be back during th' next Grand Gala!", "seasonalShopText": "Welcome t' th' Seasonal Shop!! We're stockin' springtime Seasonal Edition goodies at th' moment. Everything here will be available t' purchase durin' th' Spring Fling event each year, but we're only open until April 30th, so be sure t' stock up now, or you'll have t' wait a year t' buy these items again!", "seasonalShopSummerText": "Welcome t' th' Seasonal Shop!! We be stockin' summertime Seasonal Edition goodies at th' moment. Everything here will be available t' purchase during th' Summer Splash event each year, but we're only open until July 31st, so be sure t' stock up now, or you'll have t' wait a year t' buy these items again!", "seasonalShopRebirth": "If ye've used th' Orb o' Rebirth, ye can buy this gear in th' Loot Column after ye've unlocked the Item Shop. At first, ye'll only be able to buy the items fer yer current class (Mercenary by default), but remain stout o' heart, ye can have the other classes' stuff if ye switch to that class.", diff --git a/common/locales/en@pirate/tasks.json b/common/locales/en@pirate/tasks.json index 7e8ac25410..1701e809ac 100644 --- a/common/locales/en@pirate/tasks.json +++ b/common/locales/en@pirate/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Fortify Potion", "fortifyPop": "Return all tasks t' neutral value (yellow color), 'n restore all lost Health.", "fortify": "Fortify", - "fortifyText": "Fortify gunna return all ye tasks to a neutral (yellow) state, as if ye'd just added them, 'n top ye Health off to full. Consider 'tis an option 'o last resort! Red tasks provide jolly incentive to improve. But if all that red fills ye wit' despair, 'n th' beginnin' 'o each new day proves lethal, spend th' Gems 'n catch a reprieve!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Arrr ye sure ye want t' delete this task?", "streakCoins": "Streak Bonus!", "pushTaskToTop": "Push task to top", diff --git a/common/locales/en_GB/tasks.json b/common/locales/en_GB/tasks.json index 02aa3591fa..1c5a4cee2d 100644 --- a/common/locales/en_GB/tasks.json +++ b/common/locales/en_GB/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Fortify Potion", "fortifyPop": "Return all tasks to neutral value (yellow colour), and restore all lost Health.", "fortify": "Fortify", - "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. Consider this an option of last resort! Red tasks provide good incentive to improve. But if all that red fills you with despair, and the beginning of each new day proves lethal, spend the Gems and catch a reprieve!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Are you sure you want to delete this task?", "streakCoins": "Streak Bonus!", "pushTaskToTop": "Push task to top", diff --git a/common/locales/es/tasks.json b/common/locales/es/tasks.json index 18e8700c94..3aec9d93d7 100644 --- a/common/locales/es/tasks.json +++ b/common/locales/es/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Poción de Fortalecimiento.", "fortifyPop": "Devuelve todas sus tareas a un valor neutral (amarillo), y recupera toda la salud perdida.", "fortify": "Fortalecer", - "fortifyText": "Fortalecer devuelve todas tus tareas a un valor neutral (amarillo), como si las acabaras de añadir, y pone a tope tu salud. ¡Considera esto como tu última opción! Las tareas rojas proporcionan un incentivo para mejorar. Si tanto rojo te llena de deseperación y el comienzo de cada día se vuelve mortal, gasta las gemas y tomate un suspiro.", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "¿Estás seguro de que quieres eliminar esta tarea?", "streakCoins": "¡Bonus de Racha!", "pushTaskToTop": "Desplazar tarea al inicio", diff --git a/common/locales/es_419/tasks.json b/common/locales/es_419/tasks.json index 3d9ef1ce5e..8d4c1f5efa 100644 --- a/common/locales/es_419/tasks.json +++ b/common/locales/es_419/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Poción de fortalecimiento.", "fortifyPop": "Devuelve todas tus tareas a un valor neutral (amarillo), y recupera toda la salud perdida.", "fortify": "Fortalecer", - "fortifyText": "Fortalecer devuelve todas tus tareas a un valor neutral (amarillo), como si las acabaras de añadir, y llena a tope tu salud. ¡Considera esto como tu última opción! Las tareas rojas proporcionan un incentivo para mejorar. ¡Si tanto rojo te llena de desesperación y el comienzo de cada día resulta fatal, gasta las gemas y tómate un suspiro!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "¿Estás seguro de que quieres eliminar esta tarea?", "streakCoins": "¡Bonus de racha!", "pushTaskToTop": "Enviar tarea al tope", diff --git a/common/locales/fr/front.json b/common/locales/fr/front.json index 9606b34d78..9deda36548 100644 --- a/common/locales/fr/front.json +++ b/common/locales/fr/front.json @@ -34,7 +34,7 @@ "companyVideos": "Vidéos", "contribUse": "Utilisation exclusive aux contributeurs Habitica", "dragonsilverQuote": "Je ne peux vous dire combien d'outils de gestion du temps et des tâches j'ai essayé au cours des années ... [Habitica] est le seul outil que j'utilise qui m'a aidé à atteindre mes objectifs au lieu de simplement les lister.", - "dreimQuote": "Quand j'ai découvert [Habitica] le printemps dernier, je venais juste de rater la moitié de mes examens. Grâce aux Quotidiennes… j'ai réussi à m'organiser et à me discipliner, et j'ai finalement passé mes examens avec de bons résultats il y a un mois.", + "dreimQuote": "Quand j'ai découvert [Habitica] l'été dernier, je venais juste de rater la moitié de mes examens. Grâce aux Quotidiennes… j'ai réussi à m'organiser et à me discipliner, et, il y a un mois, j'ai finalement passé mes examens avec de bons résultats.", "elmiQuote": "Chaque matin, je me réjouis de me lever pour aller gagner des pièces d'or!", "email": "E-mail", "emailNewPass": "Envoyer un nouveau mot de passe par e-mail", @@ -55,7 +55,7 @@ "footerMobile": "Mobile", "footerSocial": "Social", "forgotPass": "Mot de passe oublié", - "frabjabulousQuote": "[Habitica] est la pour laquelle j'ai eu un boulot très bien rémunéré ... et encore plus fort, j'utilise du fil dentaire tous les jours !", + "frabjabulousQuote": "[Habitica] est la raison pour laquelle j'ai eu un boulot très bien rémunéré ... et encore plus fort, j'utilise du fil dentaire tous les jours !", "free": "Rejoindre gratuitement", "gamifyButton": "Jouez le jeu de votre vie dès aujourd'hui!", "goalSample1": "S'exercer au piano 1h", diff --git a/common/locales/fr/tasks.json b/common/locales/fr/tasks.json index 1952344e4c..fd7e770f2a 100644 --- a/common/locales/fr/tasks.json +++ b/common/locales/fr/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Potion de Fortification", "fortifyPop": "Fait revenir toutes les tâches à une valeur neutre (couleur jaune) et restaure tous les points de santé que vous aviez perdus.", "fortify": "Fortification", - "fortifyText": "La potion de fortification ramènera toutes vos tâches à un niveau neutre (jaune), comme si vous veniez de les ajouter, et remplira votre barre de santé. N'utilisez cette option qu'en dernier ressort ! Les tâches de couleur rouge sont une bonne invitation à s'améliorer. Mais si voir tout ce rouge vous remplit de désespoir et que chaque nouveau jour vous semble un danger mortel, dépenser vos gemmes et accordez vous un sursis !", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Êtes-vous sûr de vouloir supprimer cette tâche ?", "streakCoins": "Bonus de combo !", "pushTaskToTop": "Déplacer la tâche en tête de liste", diff --git a/common/locales/he/tasks.json b/common/locales/he/tasks.json index 73ce621f03..14c3632049 100644 --- a/common/locales/he/tasks.json +++ b/common/locales/he/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "שיקוי תגבור", "fortifyPop": "מחזיר את כל המשימות לערכן ההתחלתי (צבע צהוב) ומחזיר את כל הבריאות שאיבדת.", "fortify": "תגבור", - "fortifyText": "תגבור יחזיר את כל המשימות שלך למצבן הטבעי (צהוב), כאילו הוספת אותן עכשיו, וימלא את הבריאות שלך עד הסוף. חשוב/חשבי על זה כעל מוצא אחרון! משימות אדומות מספקות תמריץ טוב להשתפר. במקרה חמור, בו כל הצבע האדום הזה ממלא את ליבך ביאוש ויגון, ותחילתו של כל יום מהווה מכה קטלנית עבורך, שלם/י את אבני החן וקבל/י קצת אוויר לנשימה!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "בטוח/ה שיש צורך למחוק את המשימה הזו?", "streakCoins": "בונוס התמדה!", "pushTaskToTop": "דחיפת המשימה למעלה", diff --git a/common/locales/hu/tasks.json b/common/locales/hu/tasks.json index 8d38dbd267..39386b32de 100644 --- a/common/locales/hu/tasks.json +++ b/common/locales/hu/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Erősítőfőzet", "fortifyPop": "Visszaállít minden feladatot semleges értékűre (sárga színű), és feltölti az életerőt.", "fortify": "Megerősít", - "fortifyText": "Az Erősítés visszaállítja minden feladatodat semleges (sárga) állapotba, mintha csak most adtad volna őket hozzá, és feltölti az életerődet. Vedd úgy, hogy ez az utolsó lehetőség, amit végveszélyben használhatsz. A vörös feladatok arra bíztatnak, hogy fejleszd őket, de ha minden vörös feladat kétségbeeséssel tölt el és minden új nap halálosnak bizonyul, akkor költsd el a Drágaköveket és függeszd fel a halálbűntetést!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Biztos vagy benne, hogy törölni akarod ezt a feladatot?", "streakCoins": "Széria bónusz!", "pushTaskToTop": "Tetejére", diff --git a/common/locales/it/tasks.json b/common/locales/it/tasks.json index 6336c007a4..4dd8eac8f3 100644 --- a/common/locales/it/tasks.json +++ b/common/locales/it/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Pozione di Fortificazione", "fortifyPop": "Fa tornare tutte le attività al valore neutro (colore giallo) e ripristina tutti i punti vita persi.", "fortify": "Fortifica", - "fortifyText": "La fortificazione farà tornare tutte le attività allo stato neutro (colore giallo), proprio come se le avessi appena create, e riempirà la tua barra della vita. Considerala come ultima risorsa se la situazione si fa difficile! Le attività in rosso sono un buon incentivo per migliorare; tuttavia, se tutto quel rosso comincia a preoccuparti e l'inizio di ogni nuovo giorno rischia di essere fatale, spendi qualche gemma e datti un'altra opportunità!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Vuoi davvero eliminare questa attività?", "streakCoins": "Bonus serie!", "pushTaskToTop": "Metti in cima alla lista", diff --git a/common/locales/ja/tasks.json b/common/locales/ja/tasks.json index 8589cf0d8e..3a39fa13b9 100644 --- a/common/locales/ja/tasks.json +++ b/common/locales/ja/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "強化ポーション", "fortifyPop": "全てのタスクを通常の状態 (黄色)に戻します。加えて全ての体力を回復させます。", "fortify": "強化", - "fortifyText": "この強化は今追加したかのように、全てのタスクを中立(黄色)の状態に戻します。さらにあなたのHealthを全回復させます。このオプションは最後の手段だと考えてください!赤いタスクは成長のための良い動機を与えてくれます。しかし全ての赤いタスクがあなたを絶望でいっぱいにしてしまい、毎日の始まりが死に至るほどの苦しみであるならば、ジェムを支払って救済を得ましょう!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "本当にこのタスクを削除しても良いですか?", "streakCoins": "連続実行ボーナス!", "pushTaskToTop": "タスクを一番上に上げる", diff --git a/common/locales/nl/limited.json b/common/locales/nl/limited.json index edf468c82e..79c14f4221 100644 --- a/common/locales/nl/limited.json +++ b/common/locales/nl/limited.json @@ -12,10 +12,10 @@ "aquaticFriendsText": "Is <%= seafoam %> keer natgespetterd door groepsleden.", "valentineCard": "Valentijnskaart", "valentineCardNotes": "Stuur een Valentijnskaart naar een groepsgenoot.", - "valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"", - "valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"", - "valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"", - "valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"", + "valentine0": "\"Rozen zijn rood\nTaken zijn blauw\nIk ben blij dat ik in\nEen groep zit met jou!\"", + "valentine1": "\"Rozen zijn rood\nTakken zijn stijf\nSamen gaan wij\nOndeugd te lijf!\"", + "valentine2": "\"Rozen zijn rood\nDie dichtstijl is oud\nIk hoop dat je 't leuk vindt\nHet kostte 10 goud.\"", + "valentine3": "\"Rozen zijn rood\nEn ijsdraken blauw\nGeen schat is me liever\nDan mijn tijd met jou!\"", "adoringFriends": "Liefhebbende vrienden", "adoringFriendsText": "Aww, jij en je vriend moeten wel heel erg veel om elkaar geven! Heeft <%= cards %> Valentijnskaarten verzonden of ontvangen.", "polarBear": "IJsbeer", diff --git a/common/locales/nl/tasks.json b/common/locales/nl/tasks.json index 1fe3c713cd..a1471870da 100644 --- a/common/locales/nl/tasks.json +++ b/common/locales/nl/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Versterkingsdrankje", "fortifyPop": "Geeft alle taken een neutrale waarde (gele kleur), en herstelt alle verloren gezondheidspunten.", "fortify": "Versterk", - "fortifyText": "Versterken brengt al je taken terug tot een neutrale (gele) staat, alsof je ze net toegevoegd hebt, en tevens wordt je gezondheidsbalk weer helemaal gevuld. Beschouw dit als een laatste redmiddel! Rode taken zijn een goede motivatie om beter je best te doen. Maar als al dat rood je wanhopig maakt en het begin van elke dag dodelijk lijkt, geef dan een paar edelstenen uit aan een uitstel van executie!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Weet je zeker dat je deze taak wilt verwijderen?", "streakCoins": "seriebonus!", "pushTaskToTop": "Taak bovenaan zetten", diff --git a/common/locales/pl/tasks.json b/common/locales/pl/tasks.json index 9a61d25b02..e99f752e18 100644 --- a/common/locales/pl/tasks.json +++ b/common/locales/pl/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Mikstura wzmocnienia", "fortifyPop": "Przywraca wszystkie zadania do neutralnej wartości (żółty kolor), oraz uzdrawia całe stracone Zdrowie.", "fortify": "Wzmocnij", - "fortifyText": "Wzmocnienie przywróci wszystkie zadania do wartości neutralnej (żółty kolor), tak jakbyś dopiero co je dodał, a w dodatku uzdrowi cię do pełna. Traktuj tą opcję jako ostatnią deskę ratunku! Czerwone zadania zapewniają dobrą motywację do poprawienia się. Jednak jeśli te wszystkie czerwone zadania przyprawiają cię o rozpacz, a rozpoczęcie każdego nowego dnia jest śmiertelnie niebezpieczne, poświęć Klejnoty i poczuj ulgę!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Jesteś pewien, że chcesz usunąć to zadanie?", "streakCoins": "Premia za serię!", "pushTaskToTop": "Prześlij zadanie na górę", diff --git a/common/locales/pt/tasks.json b/common/locales/pt/tasks.json index f049ed7f55..f99fb7e80b 100644 --- a/common/locales/pt/tasks.json +++ b/common/locales/pt/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Poção de Fortificação", "fortifyPop": "Reverte todas tarefas para o valor neutro (cor amarela), e recupera toda Vida perdida.", "fortify": "Fortificar", - "fortifyText": "Fortificar reverterá todas suas tarefas para o valor neutro (amarelo), como se tivesse acabado de adicioná-las, e completará sua vida até estar cheia. Considere essa opção como último recurso! Tarefas vermelhas oferecem um grande incentivo para melhorar. Mas se todo vermelho o encher de desespero, e o começo de cada dia provar ser letal, gaste as Gemas e pegue um adiantamento!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Tem certeza de que deseja deletar essa tarefa?", "streakCoins": "Bônus de Combo!", "pushTaskToTop": "Enviar tarefa para o topo", diff --git a/common/locales/ro/tasks.json b/common/locales/ro/tasks.json index cd77b2af4a..be75d67fa0 100644 --- a/common/locales/ro/tasks.json +++ b/common/locales/ro/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Poțiune fortifiantă", "fortifyPop": "Readu toate țelurile la valoarea neutră (culoarea galbenă) și refă toată Sănătatea pierdută.", "fortify": "Fortifică", - "fortifyText": "Fortifierea va readuce toate țelurile la valoarea neutră (galben), ca și cum abia le-ai fi adăugat, și va reface Sănătatea la maxim. Consideră asta o opțiune de caz extrem! Sarcinile roșii furnizează un bun imbold pentru a progresa. Dar dacă tot roșul ăsta te umple cu disperare, și începutul fiecărei zile se dovedește letal, cheltuie Nestematele și ia o pauză!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Ești sigur că vrei să ștergi acest țel?", "streakCoins": "Bonus de realizare în șir!", "pushTaskToTop": "Urcă sarcina în vârf", diff --git a/common/locales/ru/gear.json b/common/locales/ru/gear.json index aaf135299c..2f06157076 100644 --- a/common/locales/ru/gear.json +++ b/common/locales/ru/gear.json @@ -443,7 +443,7 @@ "headArmoireBlueHairbowText": "Синий бант", "headArmoireBlueHairbowNotes": "Станьте восприимчивым, стойким и находчивым, надев этот превосходный синий бант! Увеличивает восприятие на <%= per %>, телосложение на <%= con %> и интеллект на <%= int %>. Волшебный сундук: независимый предмет.", "headArmoireRoyalCrownText": "Королевская корона", - "headArmoireRoyalCrownNotes": "Да здравствует правитель, могучий и сильный!Увеличивает силу на <%= str %>. Волшебный сундук: независимый предмет.", + "headArmoireRoyalCrownNotes": "Да здравствует правитель, могучий и сильный! Увеличивает силу на <%= str %>. Волшебный сундук: независимый предмет.", "headArmoireGoldenLaurelsText": "Золотые лавры", "headArmoireGoldenLaurelsNotes": "These golden laurels reward those who have conquered bad habits. Increases Perception and Constitution by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 2 of 3).", "headArmoireHornedIronHelmText": "Шлем Рогатого железа", diff --git a/common/locales/ru/limited.json b/common/locales/ru/limited.json index 3eaf4c751f..e0ca20777b 100644 --- a/common/locales/ru/limited.json +++ b/common/locales/ru/limited.json @@ -13,7 +13,7 @@ "valentineCard": "Валентинка", "valentineCardNotes": "Отправьте валентинку члену команды.", "valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"", - "valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"", + "valentine1": "«Розы алеют,\n\nБелеет сирень,\n\nВместе поборем\n\nПороки и лень!»", "valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"", "valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"", "adoringFriends": "Любимые друзья", diff --git a/common/locales/ru/settings.json b/common/locales/ru/settings.json index 8d1458a001..0c59d582ec 100644 --- a/common/locales/ru/settings.json +++ b/common/locales/ru/settings.json @@ -74,8 +74,8 @@ "usernameSuccess": "Имя пользователя успешно изменено", "emailSuccess": "Адрес электронной почты успешно изменен", "detachFacebook": "Восстановить регистрацию Facebook", - "detachedFacebook": "Successfully removed Facebook from your account", - "addedLocalAuth": "Successfully added local authentication", + "detachedFacebook": "Facebook успешно отсоединен вашего аккаунта", + "addedLocalAuth": "Локальная идентификация успешно добавлена", "data": "Данные", "exportData": "Экспорт данных", "emailChange1": "Чтобы изменить ваш email, пожалуйста, отправьте email на", diff --git a/common/locales/ru/tasks.json b/common/locales/ru/tasks.json index 767cd7e9e1..be78de81cb 100644 --- a/common/locales/ru/tasks.json +++ b/common/locales/ru/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Эликсир укрепления", "fortifyPop": "Вернуть все задания в нейтральное состояние (желтый цвет) и восстановить всё здоровье.", "fortify": "Укрепление", - "fortifyText": "Укрепление вернет все задания в нейтральное (желтое) состояние, как если бы вы только что добавили их, и восстановит здоровье до максимума. Считайте это крайней мерой! Красные задания дают хороший стимул для улучшения. Но если вы чувствуете, что вся эта краснота вселяет в вас безысходность, а начало каждого нового дня угрожает смертью, вы можете купить передышку за самоцветы!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Вы уверены, что хотите удалить эту задачу?", "streakCoins": "Бонус за серию!", "pushTaskToTop": "Переместить задание наверх", diff --git a/common/locales/sk/tasks.json b/common/locales/sk/tasks.json index 3c82146a92..819336c83c 100644 --- a/common/locales/sk/tasks.json +++ b/common/locales/sk/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Siloxír", "fortifyPop": "Vráť všetky úlohy do neutrálneho stavu (žltá farba) a navráť všetko stratené zdravie.", "fortify": "Posilniť", - "fortifyText": "Siloxír vráti všetky tvoje úlohy do neutrálneho (žltého) stavu, akoby si ich len teraz pridal a úplne ti doplní zdravie. Ber to ako poslednú možnosť! Červené úlohy dobre povzbudzujú k zlepšeniu. Ale ak ťa veľa červenej napĺňa zúfalstvom a začiatok každého dňa je pre teba smrtiaci, obetuj zopár drahokamov a získaj milosť.", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Si si istý, že chceš zmazať túto úlohu?", "streakCoins": "Bonus za sériu!", "pushTaskToTop": "Presuň úlohu navrch", diff --git a/common/locales/sr/tasks.json b/common/locales/sr/tasks.json index 81042736f2..6a26bd0af0 100644 --- a/common/locales/sr/tasks.json +++ b/common/locales/sr/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Napitak okrepljenja", "fortifyPop": "Vraća sve zadatke na početnu vrednost (žuta boja), i obnavlja izgubljeno zdravlje.", "fortify": "Okrepljenje", - "fortifyText": "Okrepljenje vraća sve zadatke u početno (žuto) stanje, kao da su tek napravljeni, i podiže zdravlje na maksimum. Smatrajte ovo krajnjom merom! Crveni zadaci mogu biti dobra motivacija da više potrudite. Međutim, ako vas crveno baca u očaj i smrt vam preti na početku svakog dana, draguljima možete kupiti odušak.", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Jeste li sigurni da želite da obrišete ovaj zadatak?", "streakCoins": "Bonus zbog redovnog izvršavanja zadataka!", "pushTaskToTop": "Premesti zadatak na vrh", diff --git a/common/locales/sv/tasks.json b/common/locales/sv/tasks.json index 538b6f04ca..8c090d0488 100644 --- a/common/locales/sv/tasks.json +++ b/common/locales/sv/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Stärkande Dryck", "fortifyPop": "Återställ alla uppgifter till neutralt värde (gul färg) och återställ all förlorad Hälsa.", "fortify": "Stärk", - "fortifyText": "Den Stärkande Drycken återställer alla uppgifter till ett neutralt (gult) värde, som om du nyss lagt till dem, och återställer all förlorad Hälsa. Se det här som en sista utväg! Röda uppgifter ger uppmuntran att bättra sig. Men om allt det röda fyller dig med förtvivlan och början av varje dag känns hemsk, spendera Juvelerna och bli benådad.", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Är du säker att du vill ta bort denna uppgift?", "streakCoins": "Följdbonus!", "pushTaskToTop": "Förflytta uppgift till toppen", diff --git a/common/locales/uk/tasks.json b/common/locales/uk/tasks.json index f500eb84ef..c729f4adb7 100644 --- a/common/locales/uk/tasks.json +++ b/common/locales/uk/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "Зілля підсилення", "fortifyPop": "Повернути завдання до нейтрального стану (жовтого кольору) та відновити все здоров'я.", "fortify": "Підсилитися", - "fortifyText": "Підсилення поверне всі завдання до нейтрального (жовтого) стану, так, наче Ви щойно їх створили, а здоров'я повністю відновиться. Уважайте це крайнім заходом! Червоні завдання дають добрий стимул для самовдосконалення. Але, якщо все червоне вселяє у Вас безвихідь, а кожний новий день може стати останнім, тоді Ви можете придбати собі відтермінування за самоцвіти!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "Ви справді бажаєте вилучити це завдання?", "streakCoins": "Бонус за серію!", "pushTaskToTop": "Перемістити завдання вгору", diff --git a/common/locales/zh/tasks.json b/common/locales/zh/tasks.json index 2038d784a5..ed006aaf79 100644 --- a/common/locales/zh/tasks.json +++ b/common/locales/zh/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "稳固药剂", "fortifyPop": "将所有任务恢复至中性状态(黄色),并补充所有损失的生命值。", "fortify": "稳固", - "fortifyText": "稳固会将所有的任务重置(黄色)状态,就像刚刚添加他们一样;还会回满生命值。将这个选项作为最后的方案!红色任务会为进步提供很好的动力。但是如果所有任务都变红了让你感到绝望,并且每天的伤害对你是致命的,那么花费宝石来缓解一下吧!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "你确定要删除这个任务吗?", "streakCoins": "连击奖励!", "pushTaskToTop": "将任务置于顶部", diff --git a/common/locales/zh_TW/tasks.json b/common/locales/zh_TW/tasks.json index 36058713b1..ad38bb5504 100644 --- a/common/locales/zh_TW/tasks.json +++ b/common/locales/zh_TW/tasks.json @@ -85,7 +85,7 @@ "fortifyName": "穩固藥水", "fortifyPop": "將所有任務調回中性 ( 黃色 ),並將 HP 補滿。", "fortify": "穩固", - "fortifyText": "穩固會將你的任務回復到中性狀態 ( 黃色 ,如同剛新增時),並把你的生命值補滿。把它當成無計可施時的最後王牌!紅色會為進步提供良好的動力,但如果滿目紅色讓你難過,且每天開始時都對你致命,那麼花費寶石來喘口氣吧!", + "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", "sureDelete": "你確定要刪除這個任務嗎?", "streakCoins": "連擊獎勵", "pushTaskToTop": "置頂", diff --git a/common/script/content.coffee b/common/script/content.coffee index 4187a3fde0..7416bd8948 100644 --- a/common/script/content.coffee +++ b/common/script/content.coffee @@ -549,8 +549,23 @@ api.timeTravelerStore = (owned) -> --------------------------------------------------------------- ### -api.potion = type: 'potion', text: t('potionText'), notes: t('potionNotes'), value: 25, key: 'potion' -api.armoire = type: 'armoire', text: t('armoireText'), notes: t('armoireNotesEmpty'), value: 100, key: 'armoire', canOwn: ((u)-> _.contains(u.achievements.ultimateGearSets, true)) +api.potion = + type: 'potion', + text: t('potionText'), + notes: t('potionNotes'), + value: 25, + key: 'potion' + +api.armoire = + type: 'armoire', + text: t('armoireText'), + notes: ((user, count)-> + return t('armoireNotesEmpty')() if (user.flags.armoireEmpty) + return t('armoireNotesFull')() + count + ), + value: 100, + key: 'armoire', + canOwn: ((u)-> _.contains(u.achievements.ultimateGearSets, true)) ### --------------------------------------------------------------- diff --git a/common/script/index.coffee b/common/script/index.coffee index 509c0c809e..4cdde33b0b 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -190,7 +190,6 @@ preenHistory = (history) -> Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there ### sortOrder = _.reduce content.gearTypes,((m,v,k)->m[v]=k;m), {} -#sortOrder.potion = _.size(sortOrder) #potion goes last #actually, _.sortBy puts anything else last, so this is unecessary api.updateStore = (user) -> return unless user changes= [] @@ -202,8 +201,6 @@ api.updateStore = (user) -> # Add special items (contrib gear, backer gear, etc) changes = changes.concat _.filter content.gear.flat, (v) -> v.klass in ['special','mystery','armoire'] and !user.items.gear.owned[v.key] and v.canOwn?(user) - changes.push content.potion - if user.flags.armoireEnabled then changes.push content.armoire # Return sorted store (array) _.sortBy changes, (c)->sortOrder[c.type] diff --git a/common/script/public/directives.js b/common/script/public/directives.js index fadcbca18b..2af15ff070 100644 --- a/common/script/public/directives.js +++ b/common/script/public/directives.js @@ -124,7 +124,7 @@ if(removeWatch) { - doRemoveWatch(); + doRemoveWatch(); } }; @@ -144,9 +144,9 @@ habitrpg.filter('markdown', function() { return function(input){ var html = md.toHtml(input); - + html = html.replace(' href',' target="_self" href'); - + return html; }; }); diff --git a/config.json.example b/config.json.example index a7b1660983..793dcc1f14 100644 --- a/config.json.example +++ b/config.json.example @@ -21,7 +21,6 @@ "NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID", "NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY", "GA_ID": "GA_ID", - "MIXPANEL_TOKEN": "MIXPANEL_TOKEN", "AMPLITUDE_KEY": "AMPLITUDE_KEY", "AMAZON_PAYMENTS": { "SELLER_ID": "SELLER_ID", @@ -40,6 +39,10 @@ "accessKeyId":"accessKeyId", "secretAccessKey":"secretAccessKey" }, + "TRANSIFEX_SLACK": { + "url": "https://hooks.slack.com/services/some-url", + "channel": "general" + }, "PAYPAL":{ "billing_plans": { "basic_earned":"basic_earned", diff --git a/package.json b/package.json index b387c0f638..aecc268ec3 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "icalendar": "lefnire/node-icalendar#e06da0e55901f0ba940dfadc42c158ed0b1fead9", "image-size": "~0.3.2", "in-app-purchase": "^0.2.0", - "jade": "~1.7.0", + "jade": "~1.11.0", "js2xmlparser": "~0.1.2", "lodash": "~2.4.1", "loggly": "~1.0.8", diff --git a/tasks/gulp-transifex-test.js b/tasks/gulp-transifex-test.js new file mode 100644 index 0000000000..72b51f54d6 --- /dev/null +++ b/tasks/gulp-transifex-test.js @@ -0,0 +1,188 @@ +import fs from 'fs'; +import _ from 'lodash'; +import nconf from 'nconf'; +import gulp from 'gulp'; +import request from 'superagent'; + +nconf.argv().env().file({ file: 'config.json' }); + +const LOCALES = './common/locales/'; +const ENGLISH_LOCALE = `${LOCALES}en/`; +const ALL_LANGUAGES = getArrayOfLanguages(); + +const SLACK_URL = nconf.get('TRANSIFEX_SLACK:url'); +const SLACK_CHANNEL = '#' + (nconf.get('TRANSIFEX_SLACK:channel') || 'general'); +const SLACK_USERNAME = 'Transifex'; +const SLACK_EMOJI = ':transifex:'; + +const malformedStringExceptions = { + messageDropFood: true, + armoireFood: true, + feedPet: true +} + +gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']); + +gulp.task('transifex:missingFiles', () => { + + let missingStrings = []; + + eachTranslationFile(ALL_LANGUAGES, (error) => { + if(error) { + missingStrings.push(error.path); + } + }); + + if (!_.isEmpty(missingStrings)) { + let message = 'the following files were missing from the translations folder'; + post(message, missingStrings); + } +}); + +gulp.task('transifex:missingStrings', () => { + + let missingStrings = []; + + eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => { + if (!translationString) { + let errorString = `${language} - ${filename} - ${key} - ${englishString}`; + missingStrings.push(errorString); + } + }); + + if (!_.isEmpty(missingStrings)) { + let message = 'The following strings are not translated'; + post(message, missingStrings); + } +}); + +gulp.task('transifex:malformedStrings', () => { + + let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE)); + let interpolationRegex = /<%= [a-zA-Z]* %>/g; + let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex); + + let stringsWithMalformedInterpolations = []; + let stringsWithIncorrectNumberOfInterpolations = []; + + let count = 0; + _(ALL_LANGUAGES).each(function(lang) { + + _.each(stringsToLookFor, function(strings, file) { + let translationFile = fs.readFileSync(LOCALES + lang + '/' + file); + let parsedTranslationFile = JSON.parse(translationFile); + + _.each(strings, function(value, key) { + let translationString = parsedTranslationFile[key]; + if (!translationString) return; + + let englishOccurences = stringsToLookFor[file][key]; + let translationOccurences = translationString.match(interpolationRegex); + + if (!translationOccurences) { + let malformedString = `${lang} - ${file} - ${key} - ${translationString}`; + stringsWithMalformedInterpolations.push(malformedString); + } else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) { + let missingInterploationString = `${lang} - ${file} - ${key} - ${translationString}`; + stringsWithIncorrectNumberOfInterpolations.push(missingInterploationString); + } + }); + }); + }); + + if (!_.isEmpty(stringsWithMalformedInterpolations)) { + let message = 'The following strings have malformed or missing interpolations'; + post(message, stringsWithMalformedInterpolations); + } + + if (!_.isEmpty(stringsWithIncorrectNumberOfInterpolations)) { + let message = 'The following strings have a different number of string interpolations'; + post(message, stringsWithIncorrectNumberOfInterpolations); + } +}); + +function getArrayOfLanguages() { + let languages = fs.readdirSync(LOCALES); + languages.shift(); // Remove README.md from array of languages + + return languages; +} + +function eachTranslationFile(languages, cb) { + let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE)); + + _(languages).each((lang) => { + _.each(jsonFiles, (filename) => { + try { + var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename); + var parsedTranslationFile = JSON.parse(translationFile); + } catch (err) { + return cb(err); + } + + let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename); + let parsedEnglishFile = JSON.parse(englishFile); + + cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile) + }); + }); +} + +function eachTranslationString(languages, cb) { + eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => { + if (error) return; + _.each(englishJSON, (string, key) => { + var translationString = translationJSON[key]; + cb(language, filename, key, string, translationString); + }); + }); +} + +function post(message, items) { + let formattedMessage = formatMessageForPosting(message, items); + + request.post(SLACK_URL) + .send({ + channel: SLACK_CHANNEL, + username: SLACK_USERNAME, + text: formattedMessage, + icon_emoji: SLACK_EMOJI + }) + .end((err, res) => { + if (err) console.error('Unable to post to slack', err); + }); +} + +function formatMessageForPosting(msg, items) { + let body = `*Warning:* ${msg}`; + body += '\n\n```\n'; + body += items.join('\n'); + body += '\n```'; + + return body; +} + +function getStringsWith(json, interpolationRegex) { + var strings = {}; + + _(json).each(function(file_name) { + var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name); + var parsed_json = JSON.parse(raw_file); + + strings[file_name] = {}; + _.each(parsed_json, function(value, key) { + var match = value.match(interpolationRegex); + if(match) strings[file_name][key] = match; + }); + }); + + return strings; +} + +function stripOutNonJsonFiles(collection) { + let onlyJson = _.filter(collection, (file) => { + return file.match(/[a-zA-Z]*\.json/); + }); + + return onlyJson; +} diff --git a/test/server_side/analytics.test.js b/test/server_side/analytics.test.js index 059414be23..96caeca914 100644 --- a/test/server_side/analytics.test.js +++ b/test/server_side/analytics.test.js @@ -222,7 +222,11 @@ describe('analytics', function() { stats: stats, contributor: { level: 1 }, purchased: { plan: { planId: 'foo-plan' } }, - flags: {tour: {intro: -2}} + flags: {tour: {intro: -2}}, + habits: [{_id: 'habit'}], + dailys: [{_id: 'daily'}], + todos: [{_id: 'todo'}], + rewards: [{_id: 'reward'}] }; analyticsData.user = user; @@ -248,7 +252,13 @@ describe('analytics', function() { Mana: 30, contributorLevel: 1, subscription: 'foo-plan', - tutorialComplete: true + tutorialComplete: true, + "Number Of Tasks": { + todos: 1, + dailys: 1, + habits: 1, + rewards: 1 + } } }); }); diff --git a/test/spec/controllers/tasksCtrlSpec.js b/test/spec/controllers/tasksCtrlSpec.js index 0b5ebd2cc2..28ffa79f0b 100644 --- a/test/spec/controllers/tasksCtrlSpec.js +++ b/test/spec/controllers/tasksCtrlSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe('Tasks Controller', function() { - var $rootScope, scope, user, ctrl; + var $rootScope, shared, scope, user, ctrl; beforeEach(function() { user = specHelper.newUser(); @@ -10,10 +10,10 @@ describe('Tasks Controller', function() { $provide.value('Guide', {}); }); - inject(function($rootScope, $controller){ + inject(function($rootScope, $controller, Shared){ scope = $rootScope.$new(); - + shared = Shared; $controller('RootCtrl', {$scope: scope, User: {user: user}}); ctrl = $controller('TasksCtrl', {$scope: scope, User: {user: user}}); @@ -28,4 +28,14 @@ describe('Tasks Controller', function() { }); }); }); + + describe('watch to updateStore', function() { + it('updates itemStore when user gear changes', function() { + sinon.stub(shared, 'updateStore').returns({item: true}); + user.items.gear.owned.foo = true; + + scope.$digest(); + expect(scope.itemStore).to.eql({item: true}); + }); + }); }); diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index ca726e37ec..f1767df1ac 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -184,6 +184,12 @@ describe('Analytics Service', function () { expectedProperties.Level = 24; expectedProperties.Mana = 41; expectedProperties.tutorialComplete = false; + expectedProperties["Number Of Tasks"] = { + habits: 1, + dailys: 1, + todos: 1, + rewards: 1 + }; beforeEach(function() { user._id = 'unique-user-id'; @@ -194,6 +200,10 @@ describe('Analytics Service', function () { user.stats.lvl = 24; user.stats.mp = 41; user.flags.tour.intro = 3; + user.habits = [{_id: 'habit'}]; + user.dailys = [{_id: 'daily'}]; + user.todos = [{_id: 'todo'}]; + user.rewards = [{_id: 'reward'}]; analytics.updateUser(properties); clock.tick(); @@ -221,7 +231,13 @@ describe('Analytics Service', function () { Mana: 41, contributorLevel: 1, subscription: 'unique-plan-id', - tutorialComplete: true + tutorialComplete: true, + "Number Of Tasks": { + todos: 1, + dailys: 1, + habits: 1, + rewards: 1 + } }; beforeEach(function() { @@ -235,6 +251,10 @@ describe('Analytics Service', function () { user.contributor.level = 1; user.purchased.plan.planId = 'unique-plan-id'; user.flags.tour.intro = -2; + user.habits = [{_id: 'habit'}]; + user.dailys = [{_id: 'daily'}]; + user.todos = [{_id: 'todo'}]; + user.rewards = [{_id: 'reward'}]; analytics.updateUser(); clock.tick(); diff --git a/test/transifex/malformedStrings.test.js b/test/transifex/malformedStrings.test.js deleted file mode 100644 index cd751cd752..0000000000 --- a/test/transifex/malformedStrings.test.js +++ /dev/null @@ -1,78 +0,0 @@ -var chai = require("chai") -var expect = chai.expect - -var fs = require('fs'); -var _ = require('lodash'); - -var locales = './common/locales/'; -var english = locales + 'en/'; -var interpolationRegex = /<%= [a-zA-Z]* %>/g; - -var languages = fs.readdirSync(locales); -languages.shift(); // Remove README.md from array of languages - -var jsonFiles = stripOutNonJsonFiles(fs.readdirSync(english)); -var stringsToLookFor = stringsWithInterpolations(jsonFiles); - -_(languages).each(function(lang) { - describe('the ' + lang + ' language', function() { - - _.each(stringsToLookFor, function(strings, file) { - context('string in ' + file + ' -', function() { - var translationFile = fs.readFileSync(locales + lang + '/' + file); - var parsedTranslationFile = JSON.parse(translationFile); - - var englishFile = fs.readFileSync(english + file); - var parsedEnglishFile = JSON.parse(englishFile); - - _.each(strings, function(value, key) { - var englishString = parsedEnglishFile[key]; - var translationString = parsedTranslationFile[key]; - - var EnglishOccurences = englishString.match(interpolationRegex); - var TranslationOccurences = translationString.match(interpolationRegex); - - xit(key + ' - contains an interpolation', function() { - - if(!TranslationOccurences) console.log("English String:", englishString); - expect(EnglishOccurences).to.exist; - expect(TranslationOccurences).to.exist; - }); - - if(EnglishOccurences.length > 1) { - it(key + ' - has the correct number of interpolations', function() { - if(!TranslationOccurences) console.log("English String:", englishString); - var numberOfOccurences = TranslationOccurences && TranslationOccurences.length; - expect(numberOfOccurences).to.eql(EnglishOccurences.length); - }); - } - }); - }); - }); - }); -}); - -function stringsWithInterpolations(json) { - var strings = {}; - - _(json).each(function(file_name) { - var raw_file = fs.readFileSync(english + file_name); - var parsed_json = JSON.parse(raw_file); - - strings[file_name] = {}; - _.each(parsed_json, function(value, key) { - var match = value.match(interpolationRegex); - if(match) strings[file_name][key] = true; - }); - }); - - return strings; -} - -function stripOutNonJsonFiles(collection) { - var onlyJson = _.filter(collection, function(file) { - return file.match(/[a-zA-Z]*\.json/); - }); - - return onlyJson; -} diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js index 6fecc56f57..7eee145e37 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', 'Analytics', - function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks, Analytics) { +habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Content', 'Shared', 'Guide', 'Tasks', 'Analytics', + function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Content, Shared, Guide, Tasks, Analytics) { $scope.obj = User.user; // used for task-lists $scope.user = User.user; @@ -212,10 +212,13 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N ------------------------ */ - $scope.$watchGroup(['user.items.gear.owned', 'user.flags.armoireEnabled'], function(){ + $scope.$watch('user.items.gear.owned', function(){ $scope.itemStore = Shared.updateStore(User.user); },true); + $scope.healthPotion = Content.potion; + $scope.armoire = Content.armoire; + $scope.buy = function(item) { User.user.ops.buy({params:{key:item.key}}); $rootScope.playSound('Reward'); diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index 135e7a205d..a37c94e1f9 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -108,6 +108,12 @@ properties.Mana = Math.floor(user.stats.mp); } properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2; + properties["Number Of Tasks"] = { + habits: user.habits.length, + dailys: user.dailys.length, + todos: user.todos.length, + rewards: user.rewards.length + }; if (user.contributor && user.contributor.level) properties.contributorLevel = user.contributor.level; if (user.purchased && user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; } diff --git a/website/src/analytics.js b/website/src/analytics.js index ad35adb9e7..0d56059704 100644 --- a/website/src/analytics.js +++ b/website/src/analytics.js @@ -153,6 +153,12 @@ function _formatUserData(user) { } properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2; + properties["Number Of Tasks"] = { + habits: user.habits.length, + dailys: user.dailys.length, + todos: user.todos.length, + rewards: user.rewards.length + }; if (user.contributor && user.contributor.level) { properties.contributorLevel = user.contributor.level; diff --git a/website/src/controllers/dataexport.js b/website/src/controllers/dataexport.js index 8a48ee1638..1c88d2d90f 100644 --- a/website/src/controllers/dataexport.js +++ b/website/src/controllers/dataexport.js @@ -102,13 +102,13 @@ dataexport.avatarPage = function(req, res) { User.findById(req.params.uuid).select('stats profile items achievements preferences backer contributor').exec(function(err, user){ res.render('avatar-static', { title: user.profile.name, - env: _.defaults({user:user},res.locals.habitrpg) + env: _.defaults({user:user}, res.locals.habitrpg) }); }) }; dataexport.avatarImage = function(req, res, next) { - var filename = 'avatar-'+req.params.uuid+'.png'; + var filename = 'avatars/'+req.params.uuid+'.png'; request.head('https://'+bucket+'.s3.amazonaws.com/'+filename, function(err,response,body) { // cache images for 10 minutes on aws, else upload a new one if (response.statusCode==200 && moment().diff(response.headers['last-modified'], 'minutes') < 10) diff --git a/website/src/i18n.js b/website/src/i18n.js index 82967213b9..2f0f14eeb6 100644 --- a/website/src/i18n.js +++ b/website/src/i18n.js @@ -148,7 +148,7 @@ module.exports = { // Export en strings only, temporary solution for mobile -// This is copied from middleware.js#module.exports.locals#t() +// This is copied from middlewares/locals#t() module.exports.enTranslations = function(){ // stringName and vars are the allowed parameters var language = _.find(avalaibleLanguages, {code: 'en'}); //language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); diff --git a/website/src/libs/buildManifest.js b/website/src/libs/buildManifest.js new file mode 100644 index 0000000000..e2b337860f --- /dev/null +++ b/website/src/libs/buildManifest.js @@ -0,0 +1,59 @@ +var fs = require('fs'); +var path = require('path'); +var nconf = require('nconf'); +var _ = require('lodash'); +var manifestFiles = require("../../public/manifest.json"); + +var IS_PROD = nconf.get('NODE_ENV') === 'production'; +var buildFiles = []; + +var walk = function(folder){ + var res = fs.readdirSync(folder); + + res.forEach(function(fileName){ + var file = folder + '/' + fileName; + if(fs.statSync(file).isDirectory()){ + walk(file); + }else{ + var relFolder = path.relative(path.join(__dirname, "/../../build"), folder); + var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); + + if(relFolder){ + old = relFolder + '/' + old; + fileName = relFolder + '/' + fileName; + } + + buildFiles[old] = fileName; + } + }); +}; + +walk(path.join(__dirname, "/../../build")); + +var getBuildUrl = module.exports.getBuildUrl = function(url){ + if(buildFiles[url]) return '/' + buildFiles[url]; + + return '/' + url; +}; + +module.exports.getManifestFiles = function(page){ + var files = manifestFiles[page]; + + if(!files) throw new Error("Page not found!"); + + var code = ''; + + if(IS_PROD){ + code += ''; + code += ''; + }else{ + _.each(files.css, function(file){ + code += ''; + }); + _.each(files.js, function(file){ + code += ''; + }); + } + + return code; +}; \ No newline at end of file diff --git a/website/src/middleware.js b/website/src/middleware.js deleted file mode 100644 index d0e1579d6e..0000000000 --- a/website/src/middleware.js +++ /dev/null @@ -1,249 +0,0 @@ -var nconf = require('nconf'); -var _ = require('lodash'); -var fs = require('fs'); -var path = require('path'); -var User = require('./models/user').model -var limiter = require('connect-ratelimit'); -var logging = require('./logging'); -var domainMiddleware = require('domain-middleware'); -var cluster = require('cluster'); -var i18n = require('./i18n.js'); -var shared = require('../../common'); -var request = require('request'); -var os = require('os'); -var moment = require('moment'); -var utils = require('./utils'); - -var IS_PROD = nconf.get('NODE_ENV') === 'production'; -var BASE_URL = nconf.get("BASE_URL"); - -module.exports.apiThrottle = function(app) { - if (!IS_PROD) return; - app.use(limiter({ - end:false, - catagories:{ - normal: { - // 2 req/s, but split as minutes - totalRequests: 80, - every: 60000 - } - } - })).use(function(req,res,next){ - //logging.info(res.ratelimit); - if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'}); - next(); - }); -} - -module.exports.domainMiddleware = function(server,mongoose) { - if (IS_PROD) { - var mins = 3, // how often to run this check - useAvg = false, // use average over 3 minutes, or simply the last minute's report - url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score'; - setInterval(function(){ - // see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data - request({ - url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url, - headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')} - }, function(err, response, body){ - var ts = JSON.parse(body).metric_data.metrics[0].timeslices, - score = ts[ts.length-1].values.score, - apdexBad = score < .75 || score == 1, - memory = os.freemem() / os.totalmem(), - memoryHigh = memory < 0.1; - if (/*apdexBad || */memoryHigh) throw "[Memory Leak] Apdex="+score+" Memory="+parseFloat(memory).toFixed(3)+" Time="+moment().format(); - }) - }, mins*60*1000); - } - - return domainMiddleware({ - server: { - close:function(){ - server.close(); - mongoose.connection.close(); - } - }, - killTimeout: 10000 - }); -} - -module.exports.errorHandler = function(err, req, res, next) { - //res.locals.domain.emit('error', err); - // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER) - var stack = (err.stack ? err.stack : err.message ? err.message : err) + - "\n ----------------------------\n" + - "\n\noriginalUrl: " + req.originalUrl + - "\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] + - "\n\nheaders: " + JSON.stringify(req.headers) + - "\n\nbody: " + JSON.stringify(req.body) + - (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : ""); - logging.error(stack); - /*logging.loggly({ - error: "Uncaught error", - stack: (err.stack || err.message || err), - body: req.body, headers: req.header, - auth: req.headers['x-api-user'], - originalUrl: req.originalUrl - });*/ - var message = err.message ? err.message : err; - message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); - res.json(500,{err:message}); //res.end(err.message); -} - -function isHTTP(req) { - return ( - req.headers['x-forwarded-proto'] && - req.headers['x-forwarded-proto'] !== 'https' && - IS_PROD && - BASE_URL.indexOf('https') === 0 - ); -} - -function isProxied(req) { - return ( - req.headers['x-habitica-lb'] && - req.headers['x-habitica-lb'] === 'Yes' - ); -} - -module.exports.forceSSL = function(req, res, next){ - if(isHTTP(req) && !isProxied(req)) { - return res.redirect(BASE_URL + req.url); - } - - next(); -} - -// Redirect to habitica for non-api urls -// NOTE: Currently using a static 'habitica.com' string, rather than BASE_URL, -// to make rollback easy. Eventually, BASE_URL should be migrated. - -function nonApiUrl(req) { - return req.url.search(/\/api\//) === -1; -} - -module.exports.forceHabitica = function(req, res, next) { - var ignoreRedirect = nconf.get('IGNORE_REDIRECT'); - - if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) { - return res.redirect('https://habitica.com' + req.url); - } - next(); -}; - -module.exports.cors = function(req, res, next) { - res.header("Access-Control-Allow-Origin", req.headers.origin || "*"); - res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE"); - res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key"); - if (req.method === 'OPTIONS') return res.send(200); - return next(); -}; - -var siteVersion = 1; - -module.exports.forceRefresh = function(req, res, next){ - if(req.query.siteVersion && req.query.siteVersion != siteVersion){ - return res.json(400, {needRefresh: true}); - } - - return next(); -}; - -var buildFiles = []; - -var walk = function(folder){ - var res = fs.readdirSync(folder); - - res.forEach(function(fileName){ - file = folder + '/' + fileName; - if(fs.statSync(file).isDirectory()){ - walk(file); - }else{ - var relFolder = path.relative(path.join(__dirname, "/../build"), folder); - var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); - - if(relFolder){ - old = relFolder + '/' + old; - fileName = relFolder + '/' + fileName; - } - - buildFiles[old] = fileName - } - }); -} - -walk(path.join(__dirname, "/../build")); - -var getBuildUrl = function(url){ - if(buildFiles[url]) return '/' + buildFiles[url]; - - return '/' + url; -} - -var manifestFiles = require("../public/manifest.json"); - -var getManifestFiles = function(page){ - var files = manifestFiles[page]; - - if(!files) throw new Error("Page not found!"); - - var code = ''; - - if(IS_PROD){ - code += ''; - code += ''; - }else{ - _.each(files.css, function(file){ - code += ''; - }); - _.each(files.js, function(file){ - code += ''; - }); - } - - return code; -} - -module.exports.locals = function(req, res, next) { - var language = _.find(i18n.avalaibleLanguages, {code: req.language}); - var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' - - // Load moment.js language file only when not on static pages - language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); - - var tavern = require('./models/group').tavern; - var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ')); - res.locals.habitrpg = _.merge(envVars, { - IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), - getManifestFiles: getManifestFiles, - getBuildUrl: getBuildUrl, - avalaibleLanguages: i18n.avalaibleLanguages, - language: language, - isStaticPage: isStaticPage, - translations: i18n.translations[language.code], - t: function(){ // stringName and vars are the allowed parameters - var args = Array.prototype.slice.call(arguments, 0); - args.push(language.code); - return shared.i18n.t.apply(null, args); - }, - siteVersion: siteVersion, - Content: shared.content, - mods: require('./models/user').mods, - tavern: tavern, // for world boss - worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}, - _: _, - MP_ID: nconf.get('MP_ID'), - AMAZON_PAYMENTS: { - SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), - CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID') - } - }); - - // Put query-string party (& guild but use partyInvite for backward compatibility) - // invitations into session to be handled later - try{ - req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite)) - } catch(e){} - - next(); -} diff --git a/website/src/middlewares/apiThrottle.js b/website/src/middlewares/apiThrottle.js new file mode 100644 index 0000000000..5a863c1fb7 --- /dev/null +++ b/website/src/middlewares/apiThrottle.js @@ -0,0 +1,22 @@ +var nconf = require('nconf'); +var limiter = require('connect-ratelimit'); + +var IS_PROD = nconf.get('NODE_ENV') === 'production'; + +module.exports = function(app) { + if (!IS_PROD) return; + app.use(limiter({ + end:false, + categories:{ + normal: { + // 2 req/s, but split as minutes + totalRequests: 80, + every: 60000 + } + } + })).use(function(req,res,next){ + //logging.info(res.ratelimit); + if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'}); + next(); + }); +}; \ No newline at end of file diff --git a/website/src/middlewares/cors.js b/website/src/middlewares/cors.js new file mode 100644 index 0000000000..b694aff2af --- /dev/null +++ b/website/src/middlewares/cors.js @@ -0,0 +1,7 @@ +module.exports = function(req, res, next) { + res.header("Access-Control-Allow-Origin", req.headers.origin || "*"); + res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE"); + res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key"); + if (req.method === 'OPTIONS') return res.send(200); + return next(); +}; diff --git a/website/src/middlewares/domain.js b/website/src/middlewares/domain.js new file mode 100644 index 0000000000..e62c478797 --- /dev/null +++ b/website/src/middlewares/domain.js @@ -0,0 +1,39 @@ +var nconf = require('nconf'); +var moment = require('moment'); +var domainMiddleware = require('domain-middleware'); +var os = require('os'); +var request = require('request'); + +var IS_PROD = nconf.get('NODE_ENV') === 'production'; + +module.exports = function(server,mongoose) { + if (IS_PROD) { + var mins = 3, // how often to run this check + useAvg = false, // use average over 3 minutes, or simply the last minute's report + url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score'; + setInterval(function(){ + // see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data + request({ + url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url, + headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')} + }, function(err, response, body){ + var ts = JSON.parse(body).metric_data.metrics[0].timeslices, + score = ts[ts.length-1].values.score, + apdexBad = score < .75 || score == 1, + memory = os.freemem() / os.totalmem(), + memoryHigh = memory < 0.1; + if (/*apdexBad || */memoryHigh) throw "[Memory Leak] Apdex="+score+" Memory="+parseFloat(memory).toFixed(3)+" Time="+moment().format(); + }); + }, mins*60*1000); + } + + return domainMiddleware({ + server: { + close:function(){ + server.close(); + mongoose.connection.close(); + } + }, + killTimeout: 10000 + }); +}; diff --git a/website/src/middlewares/errorHandler.js b/website/src/middlewares/errorHandler.js new file mode 100644 index 0000000000..eebf25ac6b --- /dev/null +++ b/website/src/middlewares/errorHandler.js @@ -0,0 +1,24 @@ +var logging = require('../logging'); + +module.exports = function(err, req, res, next) { + //res.locals.domain.emit('error', err); + // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER) + var stack = (err.stack ? err.stack : err.message ? err.message : err) + + "\n ----------------------------\n" + + "\n\noriginalUrl: " + req.originalUrl + + "\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] + + "\n\nheaders: " + JSON.stringify(req.headers) + + "\n\nbody: " + JSON.stringify(req.body) + + (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : ""); + logging.error(stack); + /*logging.loggly({ + error: "Uncaught error", + stack: (err.stack || err.message || err), + body: req.body, headers: req.header, + auth: req.headers['x-api-user'], + originalUrl: req.originalUrl + });*/ + var message = err.message ? err.message : err; + message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); + res.json(500,{err:message}); //res.end(err.message); +}; diff --git a/website/src/middlewares/forceRefresh.js b/website/src/middlewares/forceRefresh.js new file mode 100644 index 0000000000..e022aed2f6 --- /dev/null +++ b/website/src/middlewares/forceRefresh.js @@ -0,0 +1,9 @@ +module.exports.siteVersion = 1; + +module.exports.middleware = function(req, res, next){ + if(req.query.siteVersion && req.query.siteVersion != module.exports.siteVersion){ + return res.json(400, {needRefresh: true}); + } + + return next(); +}; diff --git a/website/src/middlewares/locals.js b/website/src/middlewares/locals.js new file mode 100644 index 0000000000..3f4de311b5 --- /dev/null +++ b/website/src/middlewares/locals.js @@ -0,0 +1,71 @@ +var nconf = require('nconf'); +var _ = require('lodash'); +var utils = require('../utils'); +var shared = require('../../../common'); +var i18n = require('../i18n.js'); +var buildManifest = require('../libs/buildManifest'); +var shared = require('../../../common'); +var forceRefresh = require('./forceRefresh'); +var tavern = require('../models/group').tavern; +var mods = require('../models/user').mods; + +// To avoid stringifying more data then we need, +// items from `env` used on the client will have to be specified in this array +var clientVars = ['language', 'isStaticPage', 'avalaibleLanguages', 'translations', + 'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID', + 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY', + 'worldDmg', 'mods', 'IS_MOBILE']; + +var env = { + getManifestFiles: buildManifest.getManifestFiles, + getBuildUrl: buildManifest.getBuildUrl, + _: _, + clientVars: clientVars, + tavern: tavern, // for world boss + mods: mods, + Content: shared.content, + siteVersion: forceRefresh.siteVersion, + avalaibleLanguages: i18n.avalaibleLanguages, + AMAZON_PAYMENTS: { + SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), + CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID') + } +}; + +'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(function(key){ + env[key] = nconf.get(key); +}); + +module.exports = function(req, res, next) { + var language = _.find(i18n.avalaibleLanguages, {code: req.language}); + var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' + + // Load moment.js language file only when not on static pages + language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); + + res.locals.habitrpg = _.assign(env, { + IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), + language: language, + isStaticPage: isStaticPage, + translations: i18n.translations[language.code], + t: function(){ // stringName and vars are the allowed parameters + var args = Array.prototype.slice.call(arguments, 0); + args.push(language.code); + return shared.i18n.t.apply(null, args); + }, + // Defined here and not outside of the middleware because tavern might be an + // empty object until the query to fetch it finishes + worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}, + }); + + // Put query-string party (& guild but use partyInvite for backward compatibility) + // invitations into session to be handled later + // TODO once we have proper logging in place, log errors here + if(req.query.partyInvite){ + try{ + req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite)); + } catch(e){} + } + + next(); +}; \ No newline at end of file diff --git a/website/src/middlewares/redirects.js b/website/src/middlewares/redirects.js new file mode 100644 index 0000000000..25b8d44fa9 --- /dev/null +++ b/website/src/middlewares/redirects.js @@ -0,0 +1,43 @@ +var nconf = require('nconf'); +var IS_PROD = nconf.get('NODE_ENV') === 'production'; +var ignoreRedirect = nconf.get('IGNORE_REDIRECT'); +var BASE_URL = nconf.get("BASE_URL"); + +function isHTTP(req) { + return ( + req.headers['x-forwarded-proto'] && + req.headers['x-forwarded-proto'] !== 'https' && + IS_PROD && + BASE_URL.indexOf('https') === 0 + ); +} + +function isProxied(req) { + return ( + req.headers['x-habitica-lb'] && + req.headers['x-habitica-lb'] === 'Yes' + ); +} + +module.exports.forceSSL = function(req, res, next){ + if(isHTTP(req) && !isProxied(req)) { + return res.redirect(BASE_URL + req.url); + } + + next(); +}; + +// Redirect to habitica for non-api urls +// NOTE: Currently using a static 'habitica.com' string, rather than BASE_URL, +// to make rollback easy. Eventually, BASE_URL should be migrated. + +function nonApiUrl(req) { + return req.url.search(/\/api\//) === -1; +} + +module.exports.forceHabitica = function(req, res, next) { + if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) { + return res.redirect('https://habitica.com' + req.url); + } + next(); +}; \ No newline at end of file diff --git a/website/src/models/group.js b/website/src/models/group.js index 6159b4de6e..cd669b5efd 100644 --- a/website/src/models/group.js +++ b/website/src/models/group.js @@ -236,7 +236,8 @@ module.exports.tavern = {}; var tavernQ = {_id:'habitrpg','quest.key':{$ne:null}}; process.nextTick(function(){ mongoose.model('Group').findOne(tavernQ,function(err,tavern){ - module.exports.tavern = tavern; + // Using _assign so we don't lose the reference to the exported tavern + _.assign(module.exports.tavern, tavern); }); }) GroupSchema.statics.tavernBoss = function(user,progress) { diff --git a/website/src/models/user.js b/website/src/models/user.js index c06765f805..fc52a48f3b 100644 --- a/website/src/models/user.js +++ b/website/src/models/user.js @@ -577,11 +577,16 @@ UserSchema.methods.unlink = function(options, cb) { module.exports.schema = UserSchema; module.exports.model = mongoose.model("User", UserSchema); +// Initially export an empty object so external requires will get +// the right object by reference when it's defined later +// Otherwise it would remain undefined if requested before the query executes +module.exports.mods = []; mongoose.model("User") .find({'contributor.admin':true}) .sort('-contributor.level -backer.npc profile.name') .select('profile contributor backer') .exec(function(err,mods){ - module.exports.mods = mods + // Using push to maintain the reference to mods + module.exports.mods.push.apply(module.exports.mods, mods); }); diff --git a/website/src/routes/apiv1.js b/website/src/routes/apiv1.js index f37619f195..5a28ef8f3b 100644 --- a/website/src/routes/apiv1.js +++ b/website/src/routes/apiv1.js @@ -5,9 +5,9 @@ var async = require('async'); var icalendar = require('icalendar'); var api = require('./../controllers/user'); var auth = require('./../controllers/auth'); -var middleware = require('../middleware'); var logging = require('./../logging'); var i18n = require('./../i18n'); +var forceRefresh = require('../middlewares/forceRefresh').middleware; /* ---------- Deprecated API ------------*/ @@ -161,7 +161,7 @@ router.post('/user/task', auth.auth, i18n.getUserLanguage, cron, api.addTask); // User router.get('/user', auth.auth, i18n.getUserLanguage, cron, api.getUser); router.post('/user/revive', auth.auth, i18n.getUserLanguage, cron, api.revive); -router.post('/user/batch-update', middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate); +router.post('/user/batch-update', forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate); function deprecated(req, res) { res.json(404, {err:'API v1 is no longer supported, please use API v2 instead (https://github.com/HabitRPG/habitrpg/blob/develop/API.md)'}); diff --git a/website/src/routes/apiv2.coffee b/website/src/routes/apiv2.coffee index 947979c8c2..082db2efa6 100644 --- a/website/src/routes/apiv2.coffee +++ b/website/src/routes/apiv2.coffee @@ -16,12 +16,11 @@ hall = require("../controllers/hall") challenges = require("../controllers/challenges") dataexport = require("../controllers/dataexport") nconf = require("nconf") -middleware = require("../middleware") cron = user.cron _ = require('lodash') content = require('../../../common').content i18n = require('../i18n') - +forceRefresh = require('../middlewares/forceRefresh').middleware module.exports = (swagger, v2) -> [path,body,query] = [swagger.pathParam, swagger.bodyParam, swagger.queryParam] @@ -326,7 +325,7 @@ module.exports = (swagger, v2) -> parameters:[ body '','The array of batch-operations to perform','object' ] - middleware: [middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite] + middleware: [forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite] action: user.batchUpdate # Tags diff --git a/website/src/routes/dataexport.js b/website/src/routes/dataexport.js index ba27957124..9fc2ded323 100644 --- a/website/src/routes/dataexport.js +++ b/website/src/routes/dataexport.js @@ -4,13 +4,13 @@ var dataexport = require('../controllers/dataexport'); var auth = require('../controllers/auth'); var nconf = require('nconf'); var i18n = require('../i18n'); -var middleware = require('../middleware.js'); +var locals = require('../middlewares/locals'); /* Data export */ router.get('/history.csv',auth.authWithSession,i18n.getUserLanguage,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes router.get('/userdata.xml',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.xml); router.get('/userdata.json',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.json); -router.get('/avatar-:uuid.html', i18n.getUserLanguage, middleware.locals, dataexport.avatarPage); -router.get('/avatar-:uuid.png', i18n.getUserLanguage, middleware.locals, dataexport.avatarImage); +router.get('/avatar-:uuid.html', i18n.getUserLanguage, locals, dataexport.avatarPage); +router.get('/avatar-:uuid.png', i18n.getUserLanguage, locals, dataexport.avatarImage); module.exports = router; diff --git a/website/src/routes/pages.js b/website/src/routes/pages.js index 4d72ad52ee..f3f59e3b9a 100644 --- a/website/src/routes/pages.js +++ b/website/src/routes/pages.js @@ -2,13 +2,13 @@ var nconf = require('nconf'); var express = require('express'); var router = new express.Router(); var _ = require('lodash'); -var middleware = require('../middleware'); +var locals = require('../middlewares/locals'); var user = require('../controllers/user'); var auth = require('../controllers/auth'); var i18n = require('../i18n'); // -------- App -------- -router.get('/', i18n.getUserLanguage, middleware.locals, function(req, res) { +router.get('/', i18n.getUserLanguage, locals, function(req, res) { if (!req.headers['x-api-user'] && !req.headers['x-api-key'] && !(req.session && req.session.userId)) return res.redirect('/static/front') @@ -23,7 +23,7 @@ router.get('/', i18n.getUserLanguage, middleware.locals, function(req, res) { var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit']; _.each(pages, function(name){ - router.get('/static/' + name, i18n.getUserLanguage, middleware.locals, function(req, res) { + router.get('/static/' + name, i18n.getUserLanguage, locals, function(req, res) { res.render('static/' + name, {env: res.locals.habitrpg}); }); }) diff --git a/website/src/server.js b/website/src/server.js index b108d13f2f..33bf720dcd 100644 --- a/website/src/server.js +++ b/website/src/server.js @@ -30,8 +30,6 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) { // Setup translations var i18n = require('./i18n'); - var middleware = require('./middleware'); - var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; var app = express(); var server = http.createServer(); @@ -89,16 +87,18 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) { var publicDir = path.join(__dirname, "/../public"); app.set("port", nconf.get('PORT')); - middleware.apiThrottle(app); - app.use(middleware.domainMiddleware(server,mongoose)); + require('./middlewares/apiThrottle')(app); + app.use(require('./middlewares/domain')(server,mongoose)); if (!isProd) app.use(express.logger("dev")); app.use(express.compress()); app.set("views", __dirname + "/../views"); app.set("view engine", "jade"); app.use(express.favicon(publicDir + '/favicon.ico')); - app.use(middleware.cors); - app.use(middleware.forceHabitica); - app.use(middleware.forceSSL); + app.use(require('./middlewares/cors')); + + var redirects = require('./middlewares/redirects'); + app.use(redirects.forceHabitica); + app.use(redirects.forceSSL); app.use(express.urlencoded()); app.use(express.json()); app.use(require('method-override')()); @@ -134,7 +134,7 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) { app.use('/api/v1', require('./routes/apiv1').middleware); app.use('/export', require('./routes/dataexport').middleware); require('./routes/apiv2.coffee')(swagger, v2); - app.use(middleware.errorHandler); + app.use(require('./middlewares/errorHandler')); server.on('request', app); server.listen(app.get("port"), function() { diff --git a/website/views/avatar-static.jade b/website/views/avatar-static.jade index 66c57040f8..9a3532f726 100644 --- a/website/views/avatar-static.jade +++ b/website/views/avatar-static.jade @@ -8,8 +8,15 @@ html(ng-app="habitrpg") meta(name='viewport', content='width=device-width, initial-scale=1.0') meta(name='apple-mobile-web-app-capable', content='yes') + // .slice(0).push('user') is to clone the array, + // to be surethat `user` is never avalaible to other requests' env + // TODO does it need only `user` in clientVars, not the others? + - + clientVars = env.clientVars.slice(0); + clientVars.push('user'); + script(type='text/javascript'). - window.env = !{JSON.stringify(env)}; + window.env = !{JSON.stringify(env._.pick(env, clientVars))}; != env.getManifestFiles("app") diff --git a/website/views/index.jade b/website/views/index.jade index 7770fb9e3c..a314059877 100644 --- a/website/views/index.jade +++ b/website/views/index.jade @@ -10,7 +10,8 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a meta(name='viewport', content='width=device-width, initial-scale=1.0') meta(name='apple-mobile-web-app-capable', content='yes') - if(env.NODE_ENV == 'production') + //- Disable NewRelic until we add it again on the server + if(env.NODE_ENV == 'production' && false) script(type='text/javascript'). window.NREUM||(NREUM={}),__nr_require=function(t,e,n){function r(n){if(!e[n]){var o=e[n]={exports:{}};t[n][0].call(o.exports,function(e){var o=t[n][1][e];return r(o?o:e)},o,o.exports)}return e[n].exports}if("function"==typeof __nr_require)return __nr_require;for(var o=0;od;d++)c[d].apply(u,n);return u}function a(t,e){f[t]=s(t).concat(e)}function s(t){return f[t]||[]}function c(){return n(e)}var f={};return{on:a,emit:e,create:c,listeners:s,_events:f}}function r(){return{}}var o="nr@context",i=t("gos");e.exports=n()},{gos:"7eSDFh"}],ee:[function(t,e){e.exports=t("QJf3ax")},{}],3:[function(t){function e(t,e,n,i,s){try{c?c-=1:r("err",[s||new UncaughtException(t,e,n)])}catch(f){try{r("ierr",[f,(new Date).getTime(),!0])}catch(u){}}return"function"==typeof a?a.apply(this,o(arguments)):!1}function UncaughtException(t,e,n){this.message=t||"Uncaught error with no additional information",this.sourceURL=e,this.line=n}function n(t){r("err",[t,(new Date).getTime()])}var r=t("handle"),o=t(5),i=t("ee"),a=window.onerror,s=!1,c=0;t("loader").features.err=!0,window.onerror=e,NREUM.noticeError=n;try{throw new Error}catch(f){"stack"in f&&(t(1),t(4),"addEventListener"in window&&t(2),window.XMLHttpRequest&&XMLHttpRequest.prototype&&XMLHttpRequest.prototype.addEventListener&&t(3),s=!0)}i.on("fn-start",function(){s&&(c+=1)}),i.on("fn-err",function(t,e,r){s&&(this.thrown=!0,n(r))}),i.on("fn-end",function(){s&&!this.thrown&&c>0&&(c-=1)}),i.on("internal-error",function(t){r("ierr",[t,(new Date).getTime(),!0])})},{1:8,2:5,3:9,4:7,5:21,ee:"QJf3ax",handle:"D5DuLP",loader:"G9z0Bl"}],4:[function(t){function e(){}if(window.performance&&window.performance.timing&&window.performance.getEntriesByType){var n=t("ee"),r=t("handle"),o=t(2);t("loader").features.stn=!0,t(1),n.on("fn-start",function(t){var e=t[0];e instanceof Event&&(this.bstStart=Date.now())}),n.on("fn-end",function(t,e){var n=t[0];n instanceof Event&&r("bst",[n,e,this.bstStart,Date.now()])}),o.on("fn-start",function(t,e,n){this.bstStart=Date.now(),this.bstType=n}),o.on("fn-end",function(t,e){r("bstTimer",[e,this.bstStart,Date.now(),this.bstType])}),n.on("pushState-start",function(){this.time=Date.now(),this.startPath=location.pathname+location.hash}),n.on("pushState-end",function(){r("bstHist",[location.pathname+location.hash,this.startPath,this.time])}),"addEventListener"in window.performance&&(window.performance.addEventListener("webkitresourcetimingbufferfull",function(){r("bstResource",[window.performance.getEntriesByType("resource")]),window.performance.webkitClearResourceTimings()},!1),window.performance.addEventListener("resourcetimingbufferfull",function(){r("bstResource",[window.performance.getEntriesByType("resource")]),window.performance.clearResourceTimings()},!1)),document.addEventListener("scroll",e,!1),document.addEventListener("keypress",e,!1),document.addEventListener("click",e,!1)}},{1:6,2:8,ee:"QJf3ax",handle:"D5DuLP",loader:"G9z0Bl"}],5:[function(t,e){function n(t){i.inPlace(t,["addEventListener","removeEventListener"],"-",r)}function r(t){return t[1]}var o=(t(1),t("ee").create()),i=t(2)(o),a=t("gos");if(e.exports=o,n(window),"getPrototypeOf"in Object){for(var s=document;s&&!s.hasOwnProperty("addEventListener");)s=Object.getPrototypeOf(s);s&&n(s);for(var c=XMLHttpRequest.prototype;c&&!c.hasOwnProperty("addEventListener");)c=Object.getPrototypeOf(c);c&&n(c)}else XMLHttpRequest.prototype.hasOwnProperty("addEventListener")&&n(XMLHttpRequest.prototype);o.on("addEventListener-start",function(t){if(t[1]){var e=t[1];"function"==typeof e?this.wrapped=t[1]=a(e,"nr@wrapped",function(){return i(e,"fn-",null,e.name||"anonymous")}):"function"==typeof e.handleEvent&&i.inPlace(e,["handleEvent"],"fn-")}}),o.on("removeEventListener-start",function(t){var e=this.wrapped;e&&(t[1]=e)})},{1:21,2:22,ee:"QJf3ax",gos:"7eSDFh"}],6:[function(t,e){var n=(t(2),t("ee").create()),r=t(1)(n);e.exports=n,r.inPlace(window.history,["pushState"],"-")},{1:22,2:21,ee:"QJf3ax"}],7:[function(t,e){var n=(t(2),t("ee").create()),r=t(1)(n);e.exports=n,r.inPlace(window,["requestAnimationFrame","mozRequestAnimationFrame","webkitRequestAnimationFrame","msRequestAnimationFrame"],"raf-"),n.on("raf-start",function(t){t[0]=r(t[0],"fn-")})},{1:22,2:21,ee:"QJf3ax"}],8:[function(t,e){function n(t,e,n){var r=t[0];"string"==typeof r&&(r=new Function(r)),t[0]=o(r,"fn-",null,n)}var r=(t(2),t("ee").create()),o=t(1)(r);e.exports=r,o.inPlace(window,["setTimeout","setInterval","setImmediate"],"setTimer-"),r.on("setTimer-start",n)},{1:22,2:21,ee:"QJf3ax"}],9:[function(t,e){function n(){c.inPlace(this,d,"fn-")}function r(t,e){c.inPlace(e,["onreadystatechange"],"fn-")}function o(t,e){return e}var i=t("ee").create(),a=t(1),s=t(2),c=s(i),f=s(a),u=window.XMLHttpRequest,d=["onload","onerror","onabort","onloadstart","onloadend","onprogress","ontimeout"];e.exports=i,window.XMLHttpRequest=function(t){var e=new u(t);try{i.emit("new-xhr",[],e),f.inPlace(e,["addEventListener","removeEventListener"],"-",function(t,e){return e}),e.addEventListener("readystatechange",n,!1)}catch(r){try{i.emit("internal-error",[r])}catch(o){}}return e},window.XMLHttpRequest.prototype=u.prototype,c.inPlace(XMLHttpRequest.prototype,["open","send"],"-xhr-",o),i.on("send-xhr-start",r),i.on("open-xhr-start",r)},{1:5,2:22,ee:"QJf3ax"}],10:[function(t){function e(t){if("string"==typeof t&&t.length)return t.length;if("object"!=typeof t)return void 0;if("undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer&&t.byteLength)return t.byteLength;if("undefined"!=typeof Blob&&t instanceof Blob&&t.size)return t.size;if("undefined"!=typeof FormData&&t instanceof FormData)return void 0;try{return JSON.stringify(t).length}catch(e){return void 0}}function n(t){var n=this.params,r=this.metrics;if(!this.ended){this.ended=!0;for(var i=0;c>i;i++)t.removeEventListener(s[i],this.listener,!1);if(!n.aborted){if(r.duration=(new Date).getTime()-this.startTime,4===t.readyState){n.status=t.status;var a=t.responseType,f="arraybuffer"===a||"blob"===a||"json"===a?t.response:t.responseText,u=e(f);if(u&&(r.rxSize=u),this.sameOrigin){var d=t.getResponseHeader("X-NewRelic-App-Data");d&&(n.cat=d.split(", ").pop())}}else n.status=0;r.cbTime=this.cbTime,o("xhr",[n,r,this.startTime])}}}function r(t,e){var n=i(e),r=t.params;r.host=n.hostname+":"+n.port,r.pathname=n.pathname,t.sameOrigin=n.sameOrigin}if(window.XMLHttpRequest&&XMLHttpRequest.prototype&&XMLHttpRequest.prototype.addEventListener&&!/CriOS/.test(navigator.userAgent)){t("loader").features.xhr=!0;var o=t("handle"),i=t(2),a=t("ee"),s=["load","error","abort","timeout"],c=s.length,f=t(1);t(4),t(3),a.on("new-xhr",function(){this.totalCbs=0,this.called=0,this.cbTime=0,this.end=n,this.ended=!1,this.xhrGuids={}}),a.on("open-xhr-start",function(t){this.params={method:t[0]},r(this,t[1]),this.metrics={}}),a.on("open-xhr-end",function(t,e){"loader_config"in NREUM&&"xpid"in NREUM.loader_config&&this.sameOrigin&&e.setRequestHeader("X-NewRelic-ID",NREUM.loader_config.xpid)}),a.on("send-xhr-start",function(t,n){var r=this.metrics,o=t[0],i=this;if(r&&o){var f=e(o);f&&(r.txSize=f)}this.startTime=(new Date).getTime(),this.listener=function(t){try{"abort"===t.type&&(i.params.aborted=!0),("load"!==t.type||i.called===i.totalCbs&&(i.onloadCalled||"function"!=typeof n.onload))&&i.end(n)}catch(e){try{a.emit("internal-error",[e])}catch(r){}}};for(var u=0;c>u;u++)n.addEventListener(s[u],this.listener,!1)}),a.on("xhr-cb-time",function(t,e,n){this.cbTime+=t,e?this.onloadCalled=!0:this.called+=1,this.called!==this.totalCbs||!this.onloadCalled&&"function"==typeof n.onload||this.end(n)}),a.on("xhr-load-added",function(t,e){var n=""+f(t)+!!e;this.xhrGuids&&!this.xhrGuids[n]&&(this.xhrGuids[n]=!0,this.totalCbs+=1)}),a.on("xhr-load-removed",function(t,e){var n=""+f(t)+!!e;this.xhrGuids&&this.xhrGuids[n]&&(delete this.xhrGuids[n],this.totalCbs-=1)}),a.on("addEventListener-end",function(t,e){e instanceof XMLHttpRequest&&"load"===t[0]&&a.emit("xhr-load-added",[t[1],t[2]],e)}),a.on("removeEventListener-end",function(t,e){e instanceof XMLHttpRequest&&"load"===t[0]&&a.emit("xhr-load-removed",[t[1],t[2]],e)}),a.on("fn-start",function(t,e,n){e instanceof XMLHttpRequest&&("onload"===n&&(this.onload=!0),("load"===(t[0]&&t[0].type)||this.onload)&&(this.xhrCbStart=(new Date).getTime()))}),a.on("fn-end",function(t,e){this.xhrCbStart&&a.emit("xhr-cb-time",[(new Date).getTime()-this.xhrCbStart,this.onload,e],e)})}},{1:"XL7HBI",2:11,3:9,4:5,ee:"QJf3ax",handle:"D5DuLP",loader:"G9z0Bl"}],11:[function(t,e){e.exports=function(t){var e=document.createElement("a"),n=window.location,r={};e.href=t,r.port=e.port;var o=e.href.split("://");return!r.port&&o[1]&&(r.port=o[1].split("/")[0].split("@").pop().split(":")[1]),r.port&&"0"!==r.port||(r.port="https"===o[0]?"443":"80"),r.hostname=e.hostname||n.hostname,r.pathname=e.pathname,r.protocol=o[0],"/"!==r.pathname.charAt(0)&&(r.pathname="/"+r.pathname),r.sameOrigin=!e.hostname||e.hostname===document.domain&&e.port===n.port&&e.protocol===n.protocol,r}},{}],gos:[function(t,e){e.exports=t("7eSDFh")},{}],"7eSDFh":[function(t,e){function n(t,e,n){if(r.call(t,e))return t[e];var o=n();if(Object.defineProperty&&Object.keys)try{return Object.defineProperty(t,e,{value:o,writable:!0,enumerable:!1}),o}catch(i){}return t[e]=o,o}var r=Object.prototype.hasOwnProperty;e.exports=n},{}],D5DuLP:[function(t,e){function n(t,e,n){return r.listeners(t).length?r.emit(t,e,n):(o[t]||(o[t]=[]),void o[t].push(e))}var r=t("ee").create(),o={};e.exports=n,n.ee=r,r.q=o},{ee:"QJf3ax"}],handle:[function(t,e){e.exports=t("D5DuLP")},{}],XL7HBI:[function(t,e){function n(t){var e=typeof t;return!t||"object"!==e&&"function"!==e?-1:t===window?0:i(t,o,function(){return r++})}var r=1,o="nr@id",i=t("gos");e.exports=n},{gos:"7eSDFh"}],id:[function(t,e){e.exports=t("XL7HBI")},{}],loader:[function(t,e){e.exports=t("G9z0Bl")},{}],G9z0Bl:[function(t,e){function n(){var t=l.info=NREUM.info;if(t&&t.licenseKey&&t.applicationID&&f&&f.body){s(h,function(e,n){e in t||(t[e]=n)}),l.proto="https"===p.split(":")[0]||t.sslForHttp?"https://":"http://",a("mark",["onload",i()]);var e=f.createElement("script");e.src=l.proto+t.agent,f.body.appendChild(e)}}function r(){"complete"===f.readyState&&o()}function o(){a("mark",["domContent",i()])}function i(){return(new Date).getTime()}var a=t("handle"),s=t(1),c=window,f=c.document,u="addEventListener",d="attachEvent",p=(""+location).split("?")[0],h={beacon:"bam.nr-data.net",errorBeacon:"bam.nr-data.net",agent:"js-agent.newrelic.com/nr-515.min.js"},l=e.exports={offset:i(),origin:p,features:{}};f[u]?(f[u]("DOMContentLoaded",o,!1),c[u]("load",n,!1)):(f[d]("onreadystatechange",r),c[d]("onload",n)),a("mark",["firstbyte",i()])},{1:20,handle:"D5DuLP"}],20:[function(t,e){function n(t,e){var n=[],o="",i=0;for(o in t)r.call(t,o)&&(n[i]=e(o,t[o]),i+=1);return n}var r=Object.prototype.hasOwnProperty;e.exports=n},{}],21:[function(t,e){function n(t,e,n){e||(e=0),"undefined"==typeof n&&(n=t?t.length:0);for(var r=-1,o=n-e||0,i=Array(0>o?0:o);++r' + env.t('tavernAlert2') + '.
' + env.t('moderatorIntro1') + //- FIXME this could be rendered server-side? span(ng-repeat='mod in env.mods') |    span(ng-if='::mod.contributor.admin',popover=env.t('gamemaster'),popover-trigger='mouseenter',popover-placement='right') diff --git a/website/views/shared/modals/limited.jade b/website/views/shared/modals/limited.jade index b993301ad3..158fd5a647 100644 --- a/website/views/shared/modals/limited.jade +++ b/website/views/shared/modals/limited.jade @@ -6,7 +6,7 @@ script(id='modals/cards.html', type='text/ng-template') .bg-info(style='padding:10px') p {{::env.t('toAndFromCard', { toName: user.profile.name, fromName: user.items.special[cardType + 'Received'][0]})}} hr - markdown(text='{{::cardMessage}}') + markdown(text='::cardMessage') .modal-footer small.pull-left {{::env.t(cardType + 'CardExplanation')}} button.btn.btn-default(ng-click='user.ops.readCard({params: {cardType: cardType}}); $close()')=env.t('ok') diff --git a/website/views/shared/tasks/task_view/static_rewards.jade b/website/views/shared/tasks/task_view/static_rewards.jade index eb737be5dd..3ce7ab78b8 100644 --- a/website/views/shared/tasks/task_view/static_rewards.jade +++ b/website/views/shared/tasks/task_view/static_rewards.jade @@ -1,18 +1,23 @@ -ul.items.rewards(ng-if='main && list.type=="reward"') - li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.key == "armoire" && !user.flags.armoireEmpty ? env.t("armoireNotesFull") + armoireCount(user.items.gear.owned) : item.notes()}}') +mixin reward(item) + li.task.reward-item(popover-trigger='mouseenter', popover-placement='top', popover='{{::#{item}.notes()}}')&attributes(attributes) // right-hand side control buttons .task-meta-controls span.task-notes span.glyphicon.glyphicon-comment //left-hand size commands .task-controls.task-primary - input.visuallyhidden( - class="reward", + input.reward.visuallyhidden( type='checkbox', - ui-keypress='{13:"buy(item)"}') - a.money.btn-buy.item-btn(ng-class='{highValue: item.value >= 1000}', ng-click='buy(item)') + ui-keypress='{13:"buy(#{item})"}') + a.money.btn-buy.item-btn(ng-class='::{highValue: #{item}.value >= 1000}', ng-click='::buy(#{item})') span.shop_gold - span.reward-cost {{item.value}} + span.reward-cost {{::#{item}.value}} // main content - span(ng-class='::{"shop_{{item.key}} shop-sprite item-img": true}').reward-img - p.task-text {{item.text()}} + span(ng-class='::{"shop_{{#{item}.key}} shop-sprite item-img": true}').reward-img + p.task-text {{::#{item}.text()}} + +ul.items.rewards(ng-if='main && list.type=="reward"') + +reward('item')(ng-repeat='item in itemStore') + +reward('healthPotion') + +reward('armoire')(ng-if='user.flags.armoireEnabled', + popover='{{armoire.notes(user, armoireCount(user.items.gear.owned))}}') diff --git a/website/views/static/front.jade b/website/views/static/front.jade index e2128e3f4c..a377ea0fe7 100644 --- a/website/views/static/front.jade +++ b/website/views/static/front.jade @@ -26,7 +26,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') link(rel='stylesheet', type='text/css', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css') script(type='text/javascript'). - window.env = !{JSON.stringify(env)}; + window.env = !{JSON.stringify(env._.pick(env, env.clientVars))}; != env.getManifestFiles("tmp_static_front") script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js') @@ -51,7 +51,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') span.icon-bar span.icon-bar a.navbar-brand(href='#') - img(src='https://s3.amazonaws.com/habitica-assets/assets/habitica_lockup2_desat.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/assets/habitica_lockup2_desat.png') // Collect the nav links, forms, and other content for toggling #bs-example-navbar-collapse-1.collapse.navbar-collapse ul.nav.navbar-nav.navbar-right @@ -69,7 +69,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') button#header-play-button.btn.btn-primary.navbar-btn.navbar-right(ng-click='playButtonClick()')= env.t('playButtonFull') #intro h1=env.t('motivate1') - img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/intro.png') + img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/intro.png') // insert intro images .introcall.bg-success h4= env.t('joinOthers') @@ -78,13 +78,13 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .presslogos.text-center = env.t('featuredIn') br - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/lifehacker.png') - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/nyt-logo.png') - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/makeuseof.png') - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Forbes_logo.png') - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Cnetlogo.png') - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Fast-Company-logo.png') - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/kickstarter-logo.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/lifehacker.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/nyt-logo.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/makeuseof.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Forbes_logo.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Cnetlogo.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Fast-Company-logo.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/kickstarter-logo.png') #intro-text .container-fluid .row @@ -96,20 +96,20 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .row .col-md-3.col-md-offset-1 h2= env.t('testimonialHeading') - img.img-say.img-responsive.visible-lg-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/publicSpaces.png') + img.img-say.img-responsive.visible-lg-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/publicSpaces.png') // Testimonials .col-md-8.col-sm-10.col-sx-10 #quote-carousel.carousel.slide(data-interval='10000', data-ride='carousel') // Bottom Carousel Indicators ol.carousel-indicators li.active(data-target='#quote-carousel', data-slide-to='0') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Drag0nsilver.png', alt='') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Drag0nsilver.png', alt='') li(data-target='#quote-carousel', data-slide-to='1') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/frabjabulous.png', alt='') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/frabjabulous.png', alt='') li(data-target='#quote-carousel', data-slide-to='2') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AndeeLiao.png', alt='') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AndeeLiao.png', alt='') li(data-target='#quote-carousel', data-slide-to='3') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AlexandraSo.png', alt='') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AlexandraSo.png', alt='') // Carousel Slides / Quotes .carousel-inner.text-center // Quote 1 @@ -201,16 +201,16 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(220, 93, 93); border-right: 1px solid rgba(0, 0, 0, 0.25);') + .task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; background-color: rgb(220, 93, 93); border-right: 0px none;') - .task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('businessSample5') - img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/coding.png') + img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/coding.png') .usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='frabjabulous', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/frabjabulous.png') + img(data-toggle='tooltip', data-placement='top', title='frabjabulous', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/frabjabulous.png') .usetweet.tweet.popover.right .arrow .popover-content = env.t('frabjabulousQuote') .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='_AlexandraSo_', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AlexandraSo.png') + img(data-toggle='tooltip', data-placement='top', title='_AlexandraSo_', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AlexandraSo.png') .usetweet.tweet.popover.right .arrow .popover-content @@ -245,16 +245,16 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(255, 207, 66); border-right: 1px solid rgba(0, 0, 0, 0.25);') + |   .task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('healthSample5') - img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/clipart-rosemonkeyct-meditation.png') + img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/clipart-rosemonkeyct-meditation.png') .usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='EvaGantz', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/EvaGantz.png') + img(data-toggle='tooltip', data-placement='top', title='EvaGantz', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/EvaGantz.png') .usetweet.tweet.popover.right .arrow .popover-content = env.t('evagantzQuote') .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='supermouse35', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/supermouse35.png') + img(data-toggle='tooltip', data-placement='top', title='supermouse35', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/supermouse35.png') .usetweet.tweet.popover.right .arrow .popover-content @@ -283,10 +283,10 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') |   .task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('teamSample3') br - img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/publicSpaces.png') + img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/publicSpaces.png') .usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='Althaire', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Althaire.png') + img(data-toggle='tooltip', data-placement='top', title='Althaire', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Althaire.png') .usetweet.tweet.popover.right .arrow .popover-content @@ -321,15 +321,15 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') + .task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; background-color: rgb(90, 98, 223); border-right: 0px none;') - .task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('schoolSample5') - img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/reading.png') + img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/reading.png') .usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='InfH', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/InfH.png') + img(data-toggle='tooltip', data-placement='top', title='InfH', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/InfH.png') .usetweet.tweet.popover.right .arrow .popover-content= env.t('infhQuote') .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='Drei-M', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Drei-M.png') + img(data-toggle='tooltip', data-placement='top', title='Drei-M', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Drei-M.png') .usetweet.tweet.popover.right .arrow .popover-content @@ -364,10 +364,10 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') + |   .task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('goalSample5') - img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png') + img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png') .usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='Kazui', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Kazui.png') + img(data-toggle='tooltip', data-placement='top', title='Kazui', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Kazui.png') .usetweet.tweet.popover.right .arrow .popover-content @@ -402,15 +402,15 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') + |   .task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('choreSample5') - img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/dusting_by_leephon.png') + img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/dusting_by_leephon.png') .usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='autumnesquirrel', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/autumnesquirrel.png') + img(data-toggle='tooltip', data-placement='top', title='autumnesquirrel', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/autumnesquirrel.png') .usetweet.tweet.popover.right .arrow .popover-content= env.t('autumnesquirrelQuote') .usetweet-group - img(data-toggle='tooltip', data-placement='top', title='irishfeet123', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/irishfeet123.png') + img(data-toggle='tooltip', data-placement='top', title='irishfeet123', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/irishfeet123.png') .usetweet.tweet.popover.right .arrow .popover-content @@ -430,7 +430,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') br = env.t('rewardByline2') .scrolltweet.hidden-xs.hidden-sm - img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Elmi', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Elmi.png') + img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Elmi', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Elmi.png') .tweet.popover.right.pull-right .arrow .popover-content= env.t('elmiQuote') @@ -440,12 +440,12 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') span.glyphicon.glyphicon-check h2 span.glyphicon.glyphicon-arrow-down - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png') h2 span.glyphicon.glyphicon-arrow-down - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/TVreward.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/TVreward.png') button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton') section#levels.container-fluid .row @@ -459,17 +459,17 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') br = env.t('unlockByline2') .scrolltweet.hidden-xs.hidden-sm - img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='16bitFil', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/16bitFil.png') + img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='16bitFil', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/16bitFil.png') .tweet.popover.right.pull-right .arrow .popover-content = env.t('sixteenBitFilQuote') .col-lg-4.col-md-4.col-lg-offset-1.col-sm-8.well.col-xs-8.col-xs-offset-2.col-sm-offset-2 .avatarscroll - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/avatar/avatarstatic.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/avatar/avatarstatic.png') h2 span.glyphicon.glyphicon-arrow-down - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/SAMPLEadventurers.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/SAMPLEadventurers.png') button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton') section#health.bg-danger.container-fluid .row @@ -480,7 +480,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') h2= env.t('punishHeading2') p.sectioninfo= env.t('punishByline') .scrolltweet.hidden-xs.hidden-sm - img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Zelah Meyer', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Zelah_Meyer.png') + img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Zelah Meyer', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Zelah_Meyer.png') .tweet.popover.right.pull-right .arrow .popover-content @@ -515,13 +515,13 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') br = env.t('questByline2') .scrolltweet.hidden-xs.hidden-sm - img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='skysailor', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/skysailor.png') + img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='skysailor', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/skysailor.png') .tweet.popover.right.pull-right .arrow .popover-content = env.t('skysailorQuote') .col-lg-4.col-md-6.col-md-offset-1.col-lg-offset-1.col-sm-10.well.col-xs-10.col-xs-offset-1.col-sm-offset-1.well-lg - img.clearfix.img-responsive.center-block(style='padding-bottom:30px;', src="https://s3.amazonaws.com/habitrpg-assets/front/images/Quest_dilatory_drag'onSmall.png") + img.clearfix.img-responsive.center-block(style='padding-bottom:30px;', src="https://d2afqr2xdmyzvu.cloudfront.net/front/images/Quest_dilatory_dragonSmall.png") .row .col-lg-5.col-md-5.col-sm-5.col-sm-offset-0.col-xs-5.col-xs-offset-0 .health-progress @@ -531,7 +531,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .bar div(style='width:20%;')   .quest-friend-img - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/party/AnnaCosplay.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/party/AnnaCosplay.png') .col-md-1.col-sm-1.col-xs-1 h2 span.glyphicon.glyphicon-arrow-right @@ -543,7 +543,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .bar div(style='width:0%;')   .quest-friend-img - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/party/GrimReaper.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/party/GrimReaper.png') button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton') section#features.bg-info.container-fluid .row @@ -551,29 +551,29 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .row .col-md-4.col-md-offset-2.col-sm-6 .feature-img - img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Mount.png') + img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Mount.png') .featuretext h4= env.t('featurePetHeading') p = env.t('featurePetByline') .col-md-4.col-sm-6 .feature-img.center-block - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/achievement-triadbingo.png') - img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/achievement-perfect.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/achievement-triadbingo.png') + img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/achievement-perfect.png') .featuretext h4= env.t('featureAchievementHeading') p= env.t('featureAchievementByline') .row .col-md-4.col-md-offset-2.col-sm-6 .feature-img - img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Promo_springclasses2015.png') + img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Promo_springclasses2015.png') .featuretext h4= env.t('featureEquipHeading') p = env.t('featureEquipByline') .col-md-4.col-sm-6 .feature-img - img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Party-Header.png') + img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Party-Header.png') .featuretext h4= env.t('featureSocialHeading') p @@ -593,19 +593,19 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') .row .col-lg-2.col-md-2.col-md-offset-1.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1 a(href='http://ionicframework.com/') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/ionic-logo-horizontal-transparent.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/ionic-logo-horizontal-transparent.png') .col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1 a(href='https://www.jetbrains.com/webstorm/') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/logo_webstorm.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/logo_webstorm.png') .col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1 a(href='http://github.com/') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/GitHub_Logo.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/GitHub_Logo.png') .col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1 a(href='https://trello.com/') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/trello-logo-blue.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/trello-logo-blue.png') .col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1 a(href='https://slack.com/') - img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/landing_slack_hash_wordmark_logo.png') + img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/landing_slack_hash_wordmark_logo.png') .row.footer-content include ../shared/footer diff --git a/website/views/static/layout.jade b/website/views/static/layout.jade index 329f84e455..63b1fc6cb6 100644 --- a/website/views/static/layout.jade +++ b/website/views/static/layout.jade @@ -10,14 +10,14 @@ html(ng-app='habitrpg') block title title=env.t('titleIndex') - if(env.NODE_ENV == 'production') + //- Disable NewRelic until we add it again on the server + if(env.NODE_ENV == 'production' && false) script(type='text/javascript'). window.NREUM||(NREUM={}),__nr_require=function(t,e,n){function r(n){if(!e[n]){var o=e[n]={exports:{}};t[n][0].call(o.exports,function(e){var o=t[n][1][e];return r(o?o:e)},o,o.exports)}return e[n].exports}if("function"==typeof __nr_require)return __nr_require;for(var o=0;od;d++)c[d].apply(u,n);return u}function a(t,e){f[t]=s(t).concat(e)}function s(t){return f[t]||[]}function c(){return n(e)}var f={};return{on:a,emit:e,create:c,listeners:s,_events:f}}function r(){return{}}var o="nr@context",i=t("gos");e.exports=n()},{gos:"7eSDFh"}],ee:[function(t,e){e.exports=t("QJf3ax")},{}],3:[function(t){function e(t,e,n,i,s){try{c?c-=1:r("err",[s||new UncaughtException(t,e,n)])}catch(f){try{r("ierr",[f,(new Date).getTime(),!0])}catch(u){}}return"function"==typeof a?a.apply(this,o(arguments)):!1}function UncaughtException(t,e,n){this.message=t||"Uncaught error with no additional information",this.sourceURL=e,this.line=n}function n(t){r("err",[t,(new Date).getTime()])}var r=t("handle"),o=t(5),i=t("ee"),a=window.onerror,s=!1,c=0;t("loader").features.err=!0,window.onerror=e,NREUM.noticeError=n;try{throw new Error}catch(f){"stack"in f&&(t(1),t(4),"addEventListener"in window&&t(2),window.XMLHttpRequest&&XMLHttpRequest.prototype&&XMLHttpRequest.prototype.addEventListener&&t(3),s=!0)}i.on("fn-start",function(){s&&(c+=1)}),i.on("fn-err",function(t,e,r){s&&(this.thrown=!0,n(r))}),i.on("fn-end",function(){s&&!this.thrown&&c>0&&(c-=1)}),i.on("internal-error",function(t){r("ierr",[t,(new Date).getTime(),!0])})},{1:8,2:5,3:9,4:7,5:21,ee:"QJf3ax",handle:"D5DuLP",loader:"G9z0Bl"}],4:[function(t){function e(){}if(window.performance&&window.performance.timing&&window.performance.getEntriesByType){var n=t("ee"),r=t("handle"),o=t(2);t("loader").features.stn=!0,t(1),n.on("fn-start",function(t){var e=t[0];e instanceof Event&&(this.bstStart=Date.now())}),n.on("fn-end",function(t,e){var n=t[0];n instanceof Event&&r("bst",[n,e,this.bstStart,Date.now()])}),o.on("fn-start",function(t,e,n){this.bstStart=Date.now(),this.bstType=n}),o.on("fn-end",function(t,e){r("bstTimer",[e,this.bstStart,Date.now(),this.bstType])}),n.on("pushState-start",function(){this.time=Date.now(),this.startPath=location.pathname+location.hash}),n.on("pushState-end",function(){r("bstHist",[location.pathname+location.hash,this.startPath,this.time])}),"addEventListener"in window.performance&&(window.performance.addEventListener("webkitresourcetimingbufferfull",function(){r("bstResource",[window.performance.getEntriesByType("resource")]),window.performance.webkitClearResourceTimings()},!1),window.performance.addEventListener("resourcetimingbufferfull",function(){r("bstResource",[window.performance.getEntriesByType("resource")]),window.performance.clearResourceTimings()},!1)),document.addEventListener("scroll",e,!1),document.addEventListener("keypress",e,!1),document.addEventListener("click",e,!1)}},{1:6,2:8,ee:"QJf3ax",handle:"D5DuLP",loader:"G9z0Bl"}],5:[function(t,e){function n(t){i.inPlace(t,["addEventListener","removeEventListener"],"-",r)}function r(t){return t[1]}var o=(t(1),t("ee").create()),i=t(2)(o),a=t("gos");if(e.exports=o,n(window),"getPrototypeOf"in Object){for(var s=document;s&&!s.hasOwnProperty("addEventListener");)s=Object.getPrototypeOf(s);s&&n(s);for(var c=XMLHttpRequest.prototype;c&&!c.hasOwnProperty("addEventListener");)c=Object.getPrototypeOf(c);c&&n(c)}else XMLHttpRequest.prototype.hasOwnProperty("addEventListener")&&n(XMLHttpRequest.prototype);o.on("addEventListener-start",function(t){if(t[1]){var e=t[1];"function"==typeof e?this.wrapped=t[1]=a(e,"nr@wrapped",function(){return i(e,"fn-",null,e.name||"anonymous")}):"function"==typeof e.handleEvent&&i.inPlace(e,["handleEvent"],"fn-")}}),o.on("removeEventListener-start",function(t){var e=this.wrapped;e&&(t[1]=e)})},{1:21,2:22,ee:"QJf3ax",gos:"7eSDFh"}],6:[function(t,e){var n=(t(2),t("ee").create()),r=t(1)(n);e.exports=n,r.inPlace(window.history,["pushState"],"-")},{1:22,2:21,ee:"QJf3ax"}],7:[function(t,e){var n=(t(2),t("ee").create()),r=t(1)(n);e.exports=n,r.inPlace(window,["requestAnimationFrame","mozRequestAnimationFrame","webkitRequestAnimationFrame","msRequestAnimationFrame"],"raf-"),n.on("raf-start",function(t){t[0]=r(t[0],"fn-")})},{1:22,2:21,ee:"QJf3ax"}],8:[function(t,e){function n(t,e,n){var r=t[0];"string"==typeof r&&(r=new Function(r)),t[0]=o(r,"fn-",null,n)}var r=(t(2),t("ee").create()),o=t(1)(r);e.exports=r,o.inPlace(window,["setTimeout","setInterval","setImmediate"],"setTimer-"),r.on("setTimer-start",n)},{1:22,2:21,ee:"QJf3ax"}],9:[function(t,e){function n(){c.inPlace(this,d,"fn-")}function r(t,e){c.inPlace(e,["onreadystatechange"],"fn-")}function o(t,e){return e}var i=t("ee").create(),a=t(1),s=t(2),c=s(i),f=s(a),u=window.XMLHttpRequest,d=["onload","onerror","onabort","onloadstart","onloadend","onprogress","ontimeout"];e.exports=i,window.XMLHttpRequest=function(t){var e=new u(t);try{i.emit("new-xhr",[],e),f.inPlace(e,["addEventListener","removeEventListener"],"-",function(t,e){return e}),e.addEventListener("readystatechange",n,!1)}catch(r){try{i.emit("internal-error",[r])}catch(o){}}return e},window.XMLHttpRequest.prototype=u.prototype,c.inPlace(XMLHttpRequest.prototype,["open","send"],"-xhr-",o),i.on("send-xhr-start",r),i.on("open-xhr-start",r)},{1:5,2:22,ee:"QJf3ax"}],10:[function(t){function e(t){if("string"==typeof t&&t.length)return t.length;if("object"!=typeof t)return void 0;if("undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer&&t.byteLength)return t.byteLength;if("undefined"!=typeof Blob&&t instanceof Blob&&t.size)return t.size;if("undefined"!=typeof FormData&&t instanceof FormData)return void 0;try{return JSON.stringify(t).length}catch(e){return void 0}}function n(t){var n=this.params,r=this.metrics;if(!this.ended){this.ended=!0;for(var i=0;c>i;i++)t.removeEventListener(s[i],this.listener,!1);if(!n.aborted){if(r.duration=(new Date).getTime()-this.startTime,4===t.readyState){n.status=t.status;var a=t.responseType,f="arraybuffer"===a||"blob"===a||"json"===a?t.response:t.responseText,u=e(f);if(u&&(r.rxSize=u),this.sameOrigin){var d=t.getResponseHeader("X-NewRelic-App-Data");d&&(n.cat=d.split(", ").pop())}}else n.status=0;r.cbTime=this.cbTime,o("xhr",[n,r,this.startTime])}}}function r(t,e){var n=i(e),r=t.params;r.host=n.hostname+":"+n.port,r.pathname=n.pathname,t.sameOrigin=n.sameOrigin}if(window.XMLHttpRequest&&XMLHttpRequest.prototype&&XMLHttpRequest.prototype.addEventListener&&!/CriOS/.test(navigator.userAgent)){t("loader").features.xhr=!0;var o=t("handle"),i=t(2),a=t("ee"),s=["load","error","abort","timeout"],c=s.length,f=t(1);t(4),t(3),a.on("new-xhr",function(){this.totalCbs=0,this.called=0,this.cbTime=0,this.end=n,this.ended=!1,this.xhrGuids={}}),a.on("open-xhr-start",function(t){this.params={method:t[0]},r(this,t[1]),this.metrics={}}),a.on("open-xhr-end",function(t,e){"loader_config"in NREUM&&"xpid"in NREUM.loader_config&&this.sameOrigin&&e.setRequestHeader("X-NewRelic-ID",NREUM.loader_config.xpid)}),a.on("send-xhr-start",function(t,n){var r=this.metrics,o=t[0],i=this;if(r&&o){var f=e(o);f&&(r.txSize=f)}this.startTime=(new Date).getTime(),this.listener=function(t){try{"abort"===t.type&&(i.params.aborted=!0),("load"!==t.type||i.called===i.totalCbs&&(i.onloadCalled||"function"!=typeof n.onload))&&i.end(n)}catch(e){try{a.emit("internal-error",[e])}catch(r){}}};for(var u=0;c>u;u++)n.addEventListener(s[u],this.listener,!1)}),a.on("xhr-cb-time",function(t,e,n){this.cbTime+=t,e?this.onloadCalled=!0:this.called+=1,this.called!==this.totalCbs||!this.onloadCalled&&"function"==typeof n.onload||this.end(n)}),a.on("xhr-load-added",function(t,e){var n=""+f(t)+!!e;this.xhrGuids&&!this.xhrGuids[n]&&(this.xhrGuids[n]=!0,this.totalCbs+=1)}),a.on("xhr-load-removed",function(t,e){var n=""+f(t)+!!e;this.xhrGuids&&this.xhrGuids[n]&&(delete this.xhrGuids[n],this.totalCbs-=1)}),a.on("addEventListener-end",function(t,e){e instanceof XMLHttpRequest&&"load"===t[0]&&a.emit("xhr-load-added",[t[1],t[2]],e)}),a.on("removeEventListener-end",function(t,e){e instanceof XMLHttpRequest&&"load"===t[0]&&a.emit("xhr-load-removed",[t[1],t[2]],e)}),a.on("fn-start",function(t,e,n){e instanceof XMLHttpRequest&&("onload"===n&&(this.onload=!0),("load"===(t[0]&&t[0].type)||this.onload)&&(this.xhrCbStart=(new Date).getTime()))}),a.on("fn-end",function(t,e){this.xhrCbStart&&a.emit("xhr-cb-time",[(new Date).getTime()-this.xhrCbStart,this.onload,e],e)})}},{1:"XL7HBI",2:11,3:9,4:5,ee:"QJf3ax",handle:"D5DuLP",loader:"G9z0Bl"}],11:[function(t,e){e.exports=function(t){var e=document.createElement("a"),n=window.location,r={};e.href=t,r.port=e.port;var o=e.href.split("://");return!r.port&&o[1]&&(r.port=o[1].split("/")[0].split("@").pop().split(":")[1]),r.port&&"0"!==r.port||(r.port="https"===o[0]?"443":"80"),r.hostname=e.hostname||n.hostname,r.pathname=e.pathname,r.protocol=o[0],"/"!==r.pathname.charAt(0)&&(r.pathname="/"+r.pathname),r.sameOrigin=!e.hostname||e.hostname===document.domain&&e.port===n.port&&e.protocol===n.protocol,r}},{}],gos:[function(t,e){e.exports=t("7eSDFh")},{}],"7eSDFh":[function(t,e){function n(t,e,n){if(r.call(t,e))return t[e];var o=n();if(Object.defineProperty&&Object.keys)try{return Object.defineProperty(t,e,{value:o,writable:!0,enumerable:!1}),o}catch(i){}return t[e]=o,o}var r=Object.prototype.hasOwnProperty;e.exports=n},{}],D5DuLP:[function(t,e){function n(t,e,n){return r.listeners(t).length?r.emit(t,e,n):(o[t]||(o[t]=[]),void o[t].push(e))}var r=t("ee").create(),o={};e.exports=n,n.ee=r,r.q=o},{ee:"QJf3ax"}],handle:[function(t,e){e.exports=t("D5DuLP")},{}],XL7HBI:[function(t,e){function n(t){var e=typeof t;return!t||"object"!==e&&"function"!==e?-1:t===window?0:i(t,o,function(){return r++})}var r=1,o="nr@id",i=t("gos");e.exports=n},{gos:"7eSDFh"}],id:[function(t,e){e.exports=t("XL7HBI")},{}],loader:[function(t,e){e.exports=t("G9z0Bl")},{}],G9z0Bl:[function(t,e){function n(){var t=l.info=NREUM.info;if(t&&t.licenseKey&&t.applicationID&&f&&f.body){s(h,function(e,n){e in t||(t[e]=n)}),l.proto="https"===p.split(":")[0]||t.sslForHttp?"https://":"http://",a("mark",["onload",i()]);var e=f.createElement("script");e.src=l.proto+t.agent,f.body.appendChild(e)}}function r(){"complete"===f.readyState&&o()}function o(){a("mark",["domContent",i()])}function i(){return(new Date).getTime()}var a=t("handle"),s=t(1),c=window,f=c.document,u="addEventListener",d="attachEvent",p=(""+location).split("?")[0],h={beacon:"bam.nr-data.net",errorBeacon:"bam.nr-data.net",agent:"js-agent.newrelic.com/nr-515.min.js"},l=e.exports={offset:i(),origin:p,features:{}};f[u]?(f[u]("DOMContentLoaded",o,!1),c[u]("load",n,!1)):(f[d]("onreadystatechange",r),c[d]("onload",n)),a("mark",["firstbyte",i()])},{1:20,handle:"D5DuLP"}],20:[function(t,e){function n(t,e){var n=[],o="",i=0;for(o in t)r.call(t,o)&&(n[i]=e(o,t[o]),i+=1);return n}var r=Object.prototype.hasOwnProperty;e.exports=n},{}],21:[function(t,e){function n(t,e,n){e||(e=0),"undefined"==typeof n&&(n=t?t.length:0);for(var r=-1,o=n-e||0,i=Array(0>o?0:o);++r