mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-07-20 04:43:54 +00:00
Merge branch 'develop' into anilanar-favicon192
This commit is contained in:
commit
025b302ec6
1476 changed files with 43443 additions and 30110 deletions
120
.eslintrc
Normal file
120
.eslintrc
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"rules": {
|
||||
"indent": [2, 2],
|
||||
"quotes": [2, "single"],
|
||||
"linebreak-style": [2, "unix"],
|
||||
"semi": [2, "always"],
|
||||
"no-extra-parens": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"block-scoped-var": 2,
|
||||
"dot-location": [2, "property"],
|
||||
"dot-notation": 2,
|
||||
"eqeqeq": 2,
|
||||
"no-caller": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-empty-pattern": 2,
|
||||
"no-empty-label": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-implicit-coercion": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-native-reassign": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-new": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-octal": 2,
|
||||
"no-process-env": 2,
|
||||
"no-proto": 2,
|
||||
"no-implied-eval": 2,
|
||||
"yoda": 2,
|
||||
"wrap-iife": 2,
|
||||
"radix": 2,
|
||||
"no-with": 2,
|
||||
"no-void": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-sequences": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-redeclare": 2,
|
||||
"strict": [2, "global"],
|
||||
"no-delete-var": 2,
|
||||
"no-label-var": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-shadow": [2, { "builtinGlobals": true }],
|
||||
"no-undef-init": 2,
|
||||
"no-undef": [2, { typeof: true }],
|
||||
"no-unused-vars": 2,
|
||||
"no-use-before-define": 2,
|
||||
"global-require": 2,
|
||||
"handle-callback-err": [2, "^.*(e|E)rr"],
|
||||
"no-path-concat": 2,
|
||||
"arrow-spacing": 2,
|
||||
"constructor-super": 2,
|
||||
"generator-star-spacing": 2,
|
||||
"no-arrow-condition": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-var": 2,
|
||||
"object-shorthand": 2,
|
||||
"prefer-const": 0,
|
||||
"prefer-spread": 2,
|
||||
"prefer-template": 2,
|
||||
"array-bracket-spacing": [2, "never"],
|
||||
"brace-style": [2, "1tbs", { "allowSingleLine": false }],
|
||||
"camelcase": 2,
|
||||
"comma-spacing": 2,
|
||||
"comma-style": [2, "last"],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"computed-property-spacing": [2, "never"],
|
||||
"consistent-this": [2, "self"],
|
||||
"func-names": 2,
|
||||
"func-style": [2, "declaration", { "allowArrowFunctions": true }],
|
||||
"block-spacing": [2, "always"],
|
||||
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
|
||||
"max-nested-callbacks": [2, 3],
|
||||
"new-cap": 2,
|
||||
"new-parens": 2,
|
||||
"newline-after-var": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-continue": 2,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-new-object": 2,
|
||||
"no-nested-ternary": 2,
|
||||
"one-var": [2, "never"],
|
||||
"operator-linebreak": [2, "after"],
|
||||
"quote-props": [2, "as-needed"],
|
||||
"semi-spacing": [2, {"before": false, "after": true}],
|
||||
"space-after-keywords": 2,
|
||||
"space-before-blocks": 2,
|
||||
"space-before-function-paren": 2,
|
||||
"space-before-keywords": 2,
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": 2,
|
||||
"spaced-comment": [2, "always", { exceptions: ["-"]}],
|
||||
"padded-blocks": [2, "never"],
|
||||
"no-multiple-empty-lines": [2, {max: 2}]
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"mocha": true,
|
||||
"node": true
|
||||
},
|
||||
ecmaFeatures : {
|
||||
modules: true
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -16,7 +16,7 @@ newrelic_agent.log
|
|||
.bower-cache
|
||||
.vagrant
|
||||
Vagrantfile
|
||||
|
||||
TODO
|
||||
*.log
|
||||
src/*/*.map
|
||||
src/*/*/*.map
|
||||
|
|
|
|||
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
0.10.40
|
||||
0.10.40
|
||||
|
|
@ -2,6 +2,8 @@ language: node_js
|
|||
node_js:
|
||||
- '0.10'
|
||||
before_install:
|
||||
- "npm install -g npm@2"
|
||||
- "npm install -g gulp"
|
||||
- "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10"
|
||||
- "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list"
|
||||
- "sudo apt-get update"
|
||||
|
|
|
|||
60
Dockerfile
60
Dockerfile
|
|
@ -1,41 +1,41 @@
|
|||
FROM ubuntu:trusty
|
||||
|
||||
MAINTAINER Thibault Cohen <titilambert@gmail.com>
|
||||
MAINTAINER Sabe Jones <sabe@habitica.com>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
### Init
|
||||
# Avoid ERROR: invoke-rc.d: policy-rc.d denied execution of start.
|
||||
RUN echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d
|
||||
|
||||
# Install prerequisites
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
libkrb5-dev \
|
||||
python
|
||||
|
||||
### Utils
|
||||
# Install NodeJS
|
||||
RUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
RUN apt-get install -y git vim graphicsmagick nodejs phantomjs npm pkgconf libcairo2-dev libjpeg8-dev
|
||||
# Clean up package management
|
||||
RUN apt-get clean
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
### Installation
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN git clone https://github.com/HabitRPG/habitrpg.git
|
||||
RUN npm install -g gulp grunt-cli bower
|
||||
RUN cd /habitrpg && npm install
|
||||
RUN cd /habitrpg && bower install --allow-root
|
||||
|
||||
RUN cd /opt && git clone https://github.com/HabitRPG/habitrpg.git
|
||||
# Create environment config file and build directory
|
||||
RUN cd /habitrpg && cp config.json.example config.json
|
||||
RUN mkdir -p /habitrpg/website/build
|
||||
|
||||
#RUN cd /opt/habitrpg && git checkout -t origin/develop
|
||||
# Point config.json to Mongo instance. Edit the IP address to your running Mongo container's IP before running.
|
||||
RUN cd /habitrpg && sed -i 's/localhost/0.0.0.0/g' config.json
|
||||
|
||||
RUN cd /opt/habitrpg && git pull
|
||||
|
||||
RUN cd /opt/habitrpg && npm install -g grunt-cli bower nodemon
|
||||
|
||||
RUN ln -s /usr/bin/nodejs /usr/bin/node
|
||||
|
||||
RUN cd /opt/habitrpg && npm install
|
||||
|
||||
# Add config file
|
||||
|
||||
ADD ./config.json /opt/habitrpg/
|
||||
|
||||
RUN mkdir -p /opt/habitrpg/build
|
||||
|
||||
RUN cd /opt/habitrpg && bower install --allow-root
|
||||
|
||||
# Run server
|
||||
|
||||
RUN cd /opt/habitrpg && grunt build:prod
|
||||
|
||||
CMD cd /opt/habitrpg && grunt nodemon
|
||||
# Start Habitica
|
||||
EXPOSE 3000
|
||||
WORKDIR /habitrpg/
|
||||
CMD ["npm", "start"]
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
<a name="">HabitRPG</a>
|
||||
# (2014-01-28)
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
- **rebirth:** Bullet point about repurchase of limited ed gear after Rebirth
|
||||
([d3f4a561](https://github.com/habitrpg/habitrpg/commits/d3f4a561fdf137e5d8f406bae03be4fef1caff22))
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **#2003:** healer gear not showing
|
||||
([949cd97b](https://github.com/habitrpg/habitrpg/commits/949cd97b91b42e9450eba559bbfea17e239ab100))
|
||||
- **#2375:** merge in @SabreCat's stats.jade changes "More elegant show/hide setup for attribute bonuses"
|
||||
([518f200a](https://github.com/habitrpg/habitrpg/commits/518f200a8fc7373b44ed7d7b5f016d921b0746bd))
|
||||
- **beastmaster:** fixes #2557, adds opacity to previously-owned pets after they're mounted. You can earn them back again
|
||||
([5caaff1c](https://github.com/habitrpg/habitrpg/commits/5caaff1cea1a68fe572e7ddf4aac50248b13df5d))
|
||||
- **bosses:** don't reset progress.up when starting a new quest. We want to be able to carry over damage from the same day a boss battle begins, even if the dailies were completed before battle-start. Fixes #2168
|
||||
([4efd0f5e](https://github.com/habitrpg/habitrpg/commits/4efd0f5ed8708f2491dd483f93e3d7a268a6337d))
|
||||
- **classes:**
|
||||
- misc fixes
|
||||
([d2121a85](https://github.com/habitrpg/habitrpg/commits/d2121a858716cb5a532a53ee9c5a1adaa74a7f69))
|
||||
- misc class fixes (not @snicker, ng-if on item store since we dynamically swap it sometimes)
|
||||
([478be611](https://github.com/habitrpg/habitrpg/commits/478be6111337cd200374f7f31b959725c6a0b945))
|
||||
- **find_uniq_user:** fix
|
||||
([ecbe780e](https://github.com/habitrpg/habitrpg/commits/ecbe780e70549b1470504efe052f238c89a9db14))
|
||||
- **mounts:** Move avatar upward when mounted regardless of pet
|
||||
([bc1adeb1](https://github.com/habitrpg/habitrpg/commits/bc1adeb1277103a5ca1f756e175ed68bbe837a2f))
|
||||
- **nodemon:** ignore CHANGELOG.md on watch
|
||||
([d6c55952](https://github.com/habitrpg/habitrpg/commits/d6c55952da8b49f36e9d8e4570d80931d081343d))
|
||||
- **party:** Round boss health up instead of to nearest integer
|
||||
([626da568](https://github.com/habitrpg/habitrpg/commits/626da5681f5ea95700f8ddf40587c7184926971c),
|
||||
[#2504](https://github.com/habitrpg/habitrpg/issues/2504))
|
||||
- **paypal:** fixes #2492, remove environment check for now, only have production-mode option. revisit
|
||||
([1dc68112](https://github.com/habitrpg/habitrpg/commits/1dc68112d131e4ebdec32ddff938eb6311d6565f))
|
||||
- **profile:** fix bug where empty profile displayed on username click
|
||||
([0579c432](https://github.com/habitrpg/habitrpg/commits/0579c432489c4a038e8c9f95ea3b285f5abc146f),
|
||||
[#2465](https://github.com/habitrpg/habitrpg/issues/2465))
|
||||
- **quests:**
|
||||
- bug fix to multi-drop
|
||||
([f478d10c](https://github.com/habitrpg/habitrpg/commits/f478d10c20f816cd104b3f0da814c189957f45f5))
|
||||
- list multiple rewards in dialog
|
||||
([e48c7277](https://github.com/habitrpg/habitrpg/commits/e48c7277f8256cf827790aece51e897fe0439374))
|
||||
- **settings:** reintroduce space between captions and help bubbles stripped during localization
|
||||
([5ddf09fe](https://github.com/habitrpg/habitrpg/commits/5ddf09fe13c7f8d844c8c47be0fb8f8b2fd1df33))
|
||||
- **spells:**
|
||||
- more $rootScope spell-casting bug fixes
|
||||
([47bd6dcb](https://github.com/habitrpg/habitrpg/commits/47bd6dcb79778d90d6f3ddeb003c3d8e45433333))
|
||||
- add some spells tests, don't send up body to spell paths
|
||||
([e0646bb9](https://github.com/habitrpg/habitrpg/commits/e0646bb98d44b6874b5259107c9be5fa34c58933))
|
||||
- some $rootScope.applying action fixes so cast-ending is immediate instead of waiting on response. Also, slim down party population to the essentials to avoid RequestEntityTooLarge
|
||||
([c6f7ab8a](https://github.com/habitrpg/habitrpg/commits/c6f7ab8a5c6f4e382208a928b90ba5f4eba9cd37))
|
||||
- <ESC> to cancel spell-casting
|
||||
([a1df41ad](https://github.com/habitrpg/habitrpg/commits/a1df41ad8165cd9eb6d2d5d59c7fe404edde716c))
|
||||
- **stable:** show hatchable combo when petOwned>0 (fyi @deilann)
|
||||
([51bff238](https://github.com/habitrpg/habitrpg/commits/51bff23885ca0080e7e71ff752daa0950ae923ae))
|
||||
- **stats:** Better layout for attribute point allocation
|
||||
([d782fc6b](https://github.com/habitrpg/habitrpg/commits/d782fc6b6a3cd7e90d327c93a5764626b2990c74))
|
||||
- **tests:**
|
||||
- include select2 in test manifest
|
||||
([38b4cea7](https://github.com/habitrpg/habitrpg/commits/38b4cea73299f51c4db7f6b2eb12533d219745f8))
|
||||
- don't use cluster in tests, else we get "connection refused"
|
||||
([7a479098](https://github.com/habitrpg/habitrpg/commits/7a479098dc6535654e322c737d80813790967941)
|
||||
20
Gruntfile.js
20
Gruntfile.js
|
|
@ -1,4 +1,5 @@
|
|||
/*global module:false*/
|
||||
require('babel-core/register');
|
||||
var _ = require('lodash');
|
||||
module.exports = function(grunt) {
|
||||
|
||||
|
|
@ -51,17 +52,6 @@ module.exports = function(grunt) {
|
|||
}
|
||||
},
|
||||
|
||||
browserify: {
|
||||
dist: {
|
||||
src: ["common/index.js"],
|
||||
dest: "common/dist/scripts/habitrpg-shared.js"
|
||||
},
|
||||
options: {
|
||||
transform: ['coffeeify']
|
||||
//debug: true Huge data uri source map (400kb!)
|
||||
}
|
||||
},
|
||||
|
||||
copy: {
|
||||
build: {
|
||||
files: [
|
||||
|
|
@ -136,13 +126,12 @@ module.exports = function(grunt) {
|
|||
});
|
||||
|
||||
// Register tasks.
|
||||
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'browserify', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
|
||||
grunt.registerTask('build:dev', ['browserify', 'cssmin', 'stylus']);
|
||||
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
|
||||
grunt.registerTask('build:dev', ['cssmin', 'stylus']);
|
||||
grunt.registerTask('build:test', ['test:prepare:translations', 'build:dev']);
|
||||
|
||||
grunt.registerTask('test:prepare:translations', function() {
|
||||
require('coffee-script');
|
||||
var i18n = require('./website/src/i18n'),
|
||||
var i18n = require('./website/src/libs/i18n'),
|
||||
fs = require('fs');
|
||||
fs.writeFileSync('test/spec/mocks/translations.js',
|
||||
"if(!window.env) window.env = {};\n" +
|
||||
|
|
@ -150,7 +139,6 @@ module.exports = function(grunt) {
|
|||
});
|
||||
|
||||
// Load tasks
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-stylus');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
Habitica [](https://travis-ci.org/HabitRPG/habitrpg) [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://coveralls.io/r/HabitRPG/habitrpg?branch=develop) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) [](https://gemnasium.com/HabitRPG/habitrpg)
|
||||
Habitica [](https://travis-ci.org/HabitRPG/habitrpg) [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://coveralls.io/r/HabitRPG/habitrpg?branch=develop) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
||||
===============
|
||||
|
||||
[Habitica](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.
|
||||
|
||||
We need more programmers! Your assistance will be greatly appreciated.
|
||||
|
||||
For an introduction to the technologies used and how the software is organised, refer to [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29) - "Coders (Web & Mobile)" section.
|
||||
For an introduction to the technologies used and how the software is organized, refer to [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29) - "Coders (Web & Mobile)" section.
|
||||
|
||||
To set up a local install of Habitica for development and testing, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally), which contains instructions for Windows, *nix / Mac OS, and Vagrant.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
<a name="">My app - Changelog</a>
|
||||
# (2015-05-08)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Spring:** WHO IS LESLIE
|
||||
([6685e935](watch/commits/6685e93554a1274dedb55dae054e787fb80eb440))
|
||||
- **invite-friends:** text should be valid for both parties and guilds
|
||||
([54e82a14](watch/commits/54e82a14a252c3b9923449a7ef7cee6033a5d160))
|
||||
- **mystery:** It's 2015 now, Sabe
|
||||
([00252f20](watch/commits/00252f200481f06de9bccd1e55275d5366b03919))
|
||||
- **scoring:** move gainMP into score
|
||||
([2fc0cb8f](watch/commits/2fc0cb8fa1b3b5975c16653cb110be2f03b5427e))
|
||||
- **slimes:** Tweaks and missing sprite
|
||||
([7d1a58ca](watch/commits/7d1a58ca002af9dac19aba65d6465bd23b28d649))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **Spring:** Flung
|
||||
([d50d4ad8](watch/commits/d50d4ad8bb0f89e39ceb6562e0f8f392f94b5444))
|
||||
- **emails:** add support for weekly recap emails
|
||||
([37f7db3c](watch/commits/37f7db3c4e3859d03fd55a44e63819e273a06442))
|
||||
- **i18n:** upload japanese, serbian and chinese (taiwan)
|
||||
([ee7ba19e](watch/commits/ee7ba19ed17e72b33cbef8a324266617d384f852))
|
||||
- **mystery:** April Subscribee Items
|
||||
([7a7fc968](watch/commits/7a7fc96818ffd7f92738e8c6cc8a59e48d60597d))
|
||||
- **pets:** Slime Quest
|
||||
([f13c6cba](watch/commits/f13c6cba0026c645b19a0b1355ba2c5b27f80878))
|
||||
- **quests:** Boss damage from Habits
|
||||
([43dcded0](watch/commits/43dcded051b602d8a4efc30eef45365abfd238b4))
|
||||
- **scoring:** MP gain from Habits and Dailies
|
||||
([7b22244f](watch/commits/7b22244f0123cf649c9f2aada0811f35a565688d))
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# What's This?
|
||||
|
||||
I'm consolidating @litenull's rewrite branch with the old code, and removing files from both sources once they've been
|
||||
successfully merged into the new platform. While @litenull's "from scratch" approach was really clean, it will take
|
||||
us longer to implemente all the original features. This approach will (1) let us leverage code we already have, (2) merge
|
||||
in litenull's hard work from the last few weeks.
|
||||
|
||||
Once this archive/ directory is completely empty, we should be fully merged and ready to deploy the rewrite!
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
_ = require 'lodash'
|
||||
moment = require 'moment'
|
||||
|
||||
###
|
||||
Loads JavaScript files from public/vendor/*
|
||||
Use require() to min / concatinate for faster page load
|
||||
###
|
||||
loadJavaScripts = (model) ->
|
||||
|
||||
# Turns out you can't have expressions in browserify require() statements
|
||||
#vendor = '../../public/vendor'
|
||||
#require "#{vendor}/jquery-ui-1.10.2/jquery-1.9.1"
|
||||
|
||||
###
|
||||
Internal Scripts
|
||||
###
|
||||
require "../vendor/jquery.cookie.min.js"
|
||||
require "../vendor/bootstrap/js/bootstrap.min.js"
|
||||
require "../vendor/datepicker/js/bootstrap-datepicker"
|
||||
require "../vendor/bootstrap-tour/bootstrap-tour"
|
||||
|
||||
unless (model.get('_mobileDevice') is true)
|
||||
require "../vendor/sticky"
|
||||
|
||||
# note: external script loading is handled in app.on('render') near the bottom of this file (see https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/x8FwdTLEuXo)
|
||||
|
||||
|
||||
# jquery sticky header on scroll, no need for position fixed
|
||||
initStickyHeader = (model) ->
|
||||
$('.header-wrap').sticky({topSpacing:0})
|
||||
|
||||
|
||||
module.exports.app = (appExports, model, app) ->
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
#restoreRefs(model)
|
||||
unless model.get('_mobileDevice')
|
||||
setupTooltips(model)
|
||||
initStickyHeader(model)
|
||||
setupSortable(model)
|
||||
|
||||
$('.datepicker').datepicker({autoclose:true, todayBtn:true})
|
||||
.on 'changeDate', (ev) ->
|
||||
#for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
|
||||
model.at(ev.target).set 'date', moment(ev.date).format('MM/DD/YYYY')
|
||||
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
_ = require 'lodash'
|
||||
{helpers} = require 'habitrpg-shared'
|
||||
async = require 'async'
|
||||
|
||||
module.exports.app = (app) ->
|
||||
|
||||
###
|
||||
Sync any updates to challenges since last refresh. Do it after cron, so we don't punish them for new tasks
|
||||
This is challenge->user sync. user->challenge happens when user interacts with their tasks
|
||||
###
|
||||
app.on 'ready', (model) ->
|
||||
window.setTimeout ->
|
||||
_.each model.get('groups'), (g) ->
|
||||
if (@uid in g.members) and g.challenges
|
||||
_.each(g.challenges, ->app.challenges.syncChalToUser g)
|
||||
true
|
||||
, 500
|
||||
|
||||
###
|
||||
Sync user to challenge (when they score, add to statistics)
|
||||
###
|
||||
app.model.on "change", "_page.user.priv.tasks.*.value", (id, value, previous, passed) ->
|
||||
### Sync to challenge, but do it later ###
|
||||
async.nextTick =>
|
||||
model = app.model
|
||||
ctx = {model: model}
|
||||
task = model.at "_page.user.priv.tasks.#{id}"
|
||||
tobj = task.get()
|
||||
pub = model.get "_page.user.pub"
|
||||
|
||||
if (chalTask = helpers.taskInChallenge.call ctx, tobj)? and chalTask.get()
|
||||
chalTask.increment "value", value - previous
|
||||
chal = model.at "groups.#{tobj.group.id}.challenges.#{tobj.challenge}"
|
||||
chalUser = -> helpers.indexedAt.call(ctx, chal.path(), 'members', {id:pub.id})
|
||||
cu = chalUser()
|
||||
unless cu?.get()
|
||||
chal.push "members", {id: pub.id, name: model.get(pub.profile.name)}
|
||||
cu = model.at chalUser()
|
||||
else
|
||||
cu.set 'name', pub.profile.name # update their name incase it changed
|
||||
cu.set "#{tobj.type}s.#{tobj.id}",
|
||||
value: tobj.value
|
||||
history: tobj.history
|
||||
|
||||
###
|
||||
Render graphs for user scores when the "Challenges" tab is clicked
|
||||
###
|
||||
|
||||
###
|
||||
TODO
|
||||
1) on main tab click or party
|
||||
* sort & render graphs for party
|
||||
2) guild -> all guilds
|
||||
3) public -> all public
|
||||
###
|
||||
|
||||
|
||||
###
|
||||
$('#profile-challenges-tab-link').on 'shown', ->
|
||||
async.each _.toArray(model.get('groups')), (g) ->
|
||||
async.each _.toArray(g.challenges), (chal) ->
|
||||
async.each _.toArray(chal.tasks), (task) ->
|
||||
async.each _.toArray(chal.members), (member) ->
|
||||
if (history = member?["#{task.type}s"]?[task.id]?.history) and !!history
|
||||
data = google.visualization.arrayToDataTable _.map(history, (h)-> [h.date,h.value])
|
||||
options =
|
||||
backgroundColor: { fill:'transparent' }
|
||||
width: 150
|
||||
height: 50
|
||||
chartArea: width: '80%', height: '80%'
|
||||
axisTitlePosition: 'none'
|
||||
legend: position: 'bottom'
|
||||
hAxis: gridlines: color: 'transparent' # since you can't seem to *remove* gridlines...
|
||||
vAxis: gridlines: color: 'transparent'
|
||||
chart = new google.visualization.LineChart $(".challenge-#{chal.id}-member-#{member.id}-history-#{task.id}")[0]
|
||||
chart.draw(data, options)
|
||||
###
|
||||
|
||||
app.fn
|
||||
challenges:
|
||||
|
||||
###
|
||||
Create
|
||||
###
|
||||
create: (e,el) ->
|
||||
[type, gid] = [$(el).attr('data-type'), $(el).attr('data-gid')]
|
||||
cid = @model.id()
|
||||
@model.set '_page.new.challenge',
|
||||
id: cid
|
||||
name: ''
|
||||
habits: []
|
||||
dailys: []
|
||||
todos: []
|
||||
rewards: []
|
||||
user:
|
||||
uid: @uid
|
||||
name: @pub.get('profile.name')
|
||||
group: {type, id:gid}
|
||||
timestamp: +new Date
|
||||
|
||||
###
|
||||
Save
|
||||
###
|
||||
save: ->
|
||||
newChal = @model.get('_page.new.challenge')
|
||||
[gid, cid] = [newChal.group.id, newChal.id]
|
||||
@model.push "_page.lists.challenges.#{gid}", newChal, ->
|
||||
app.browser.growlNotification('Challenge Created','success')
|
||||
app.challenges.discard()
|
||||
app.browser.resetDom() # something is going absolutely haywire here, all model data at end of reflist after chal created
|
||||
|
||||
###
|
||||
Toggle Edit
|
||||
###
|
||||
toggleEdit: (e, el) ->
|
||||
path = "_page.editing.challenges.#{$(el).attr('data-id')}"
|
||||
@model.set path, !@model.get(path)
|
||||
|
||||
###
|
||||
Discard
|
||||
###
|
||||
discard: ->
|
||||
@model.del '_page.new.challenge'
|
||||
|
||||
###
|
||||
Delete
|
||||
###
|
||||
delete: (e) ->
|
||||
return unless confirm("Delete challenge, are you sure?") is true
|
||||
e.at().remove()
|
||||
|
||||
###
|
||||
Add challenge name as a tag for user
|
||||
###
|
||||
syncChalToUser: (chal) ->
|
||||
return unless chal
|
||||
### Sync tags ###
|
||||
tags = @priv.get('tags') or []
|
||||
idx = _.findIndex tags, {id: chal.id}
|
||||
if ~idx and (tags[idx].name isnt chal.name)
|
||||
### update the name - it's been changed since ###
|
||||
@priv.set "tags.#{idx}.name", chal.name
|
||||
else
|
||||
@priv.push 'tags', {id: chal.id, name: chal.name, challenge: true}
|
||||
|
||||
tags = {}; tags[chal.id] = true
|
||||
_.each chal.habits.concat(chal.dailys.concat(chal.todos.concat(chal.rewards))), (task) =>
|
||||
_.defaults task, { tags, challenge: chal.id, group: {id: chal.group.id, type: chal.group.type} }
|
||||
path = "tasks.#{task.id}"
|
||||
if @priv.get path
|
||||
@priv.set path, _.defaults(@priv.get(path), task)
|
||||
else
|
||||
@model.push "_page.lists.tasks.#{@uid}.#{task.type}s", task
|
||||
true
|
||||
|
||||
###
|
||||
Subscribe
|
||||
###
|
||||
subscribe: (e) ->
|
||||
chal = e.get()
|
||||
### Add all challenge's tasks to user's tasks ###
|
||||
currChallenges = @pub.get('challenges')
|
||||
@pub.unshift('challenges', chal.id) unless currChallenges and ~currChallenges.indexOf(chal.id)
|
||||
e.at().push "members",
|
||||
id: @uid
|
||||
name: @pub.get('profile.name')
|
||||
app.challenges.syncChalToUser(chal)
|
||||
|
||||
###
|
||||
--------------------------
|
||||
Unsubscribe functions
|
||||
--------------------------
|
||||
###
|
||||
|
||||
unsubscribe: (chal, keep=true) ->
|
||||
|
||||
### Remove challenge from user ###
|
||||
i = @pub.get('challenges')?.indexOf(chal.id)
|
||||
if i? and ~i
|
||||
@pub.remove("challenges", i, 1)
|
||||
|
||||
### Remove user from challenge ###
|
||||
if ~(i = _.findIndex chal.members, {id: @uid})
|
||||
@model.remove "groups.#{chal.group.id}.challenges.#{chal.id}.members", i, 1
|
||||
|
||||
### Remove tasks from user ###
|
||||
async.each chal.habits.concat(chal.dailys.concat(chal.todos.concat(chal.rewards))), (task) =>
|
||||
if keep is true
|
||||
@priv.del "tasks.#{task.id}.challenge"
|
||||
else
|
||||
path = "_page.lists.tasks.#{@uid}.#{task.type}s"
|
||||
if ~(i = _.findIndex(@model.get(path), {id:task.id}))
|
||||
@model.remove(path, i, 1)
|
||||
true
|
||||
|
||||
taskUnsubscribe: (e, el) ->
|
||||
|
||||
###
|
||||
since the challenge was deleted, we don't have its data to unsubscribe from - but we have the vestiges on the task
|
||||
FIXME this is a really dumb way of doing this
|
||||
###
|
||||
tasks = @priv.get('tasks')
|
||||
tobj = tasks[$(el).attr("data-tid")]
|
||||
deletedChal =
|
||||
id: tobj.challenge
|
||||
members: [@uid]
|
||||
habits: _.where tasks, {type: 'habit', challenge: tobj.challenge}
|
||||
dailys: _.where tasks, {type: 'daily', challenge: tobj.challenge}
|
||||
todos: _.where tasks, {type: 'todo', challenge: tobj.challenge}
|
||||
rewards: _.where tasks, {type: 'reward', challenge: tobj.challenge}
|
||||
|
||||
switch $(el).attr('data-action')
|
||||
when 'keep'
|
||||
@priv.del "tasks.#{tobj.id}.challenge"
|
||||
@priv.del "tasks.#{tobj.id}.group"
|
||||
when 'keep-all'
|
||||
app.challenges.unsubscribe.call @, deletedChal, true
|
||||
when 'remove'
|
||||
path = "_page.lists.tasks.#{@uid}.#{tobj.type}s"
|
||||
if ~(i = _.findIndex @model.get(path), {id: tobj.id})
|
||||
@model.remove path, i
|
||||
when 'remove-all'
|
||||
app.challenges.unsubscribe.call @, deletedChal, false
|
||||
|
||||
challengeUnsubscribe: (e, el) ->
|
||||
$(el).popover('destroy').popover({
|
||||
html: true
|
||||
placement: 'top'
|
||||
trigger: 'manual'
|
||||
title: 'Unsubscribe From Challenge And:'
|
||||
content: """
|
||||
<a class=challenge-unsubscribe-and-remove>Remove Tasks</a><br/>
|
||||
<a class=challenge-unsubscribe-and-keep>Keep Tasks</a><br/>
|
||||
<a class=challenge-unsubscribe-cancel>Cancel</a><br/>
|
||||
"""
|
||||
}).popover('show')
|
||||
$('.challenge-unsubscribe-and-remove').click => app.challenges.unsubscribe.call @, e.get(), false
|
||||
$('.challenge-unsubscribe-and-keep').click => app.challenges.unsubscribe.call @, e.get(), true
|
||||
$('[class^=challenge-unsubscribe]').click => $(el).popover('destroy')
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
_ = require 'lodash'
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at('_user')
|
||||
|
||||
appExports.filtersDeleteTag = (e, el) ->
|
||||
tags = user.get('tags')
|
||||
tag = e.at "_user.tags." + $(el).attr('data-index')
|
||||
tagId = tag.get('id')
|
||||
|
||||
###something got corrupted, let's clear the corrupt tags###
|
||||
unless tagId
|
||||
user.set 'tags', _.filter( tags, ((t)-> t?.id) )
|
||||
user.set 'filters', {}
|
||||
return
|
||||
|
||||
model.del "_user.filters.#{tagId}"
|
||||
tag.remove()
|
||||
|
||||
### remove tag from all tasks###
|
||||
_.each user.get("tasks"), (task) -> user.del "tasks.#{task.id}.tags.#{tagId}"; true
|
||||
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
_ = require('lodash')
|
||||
helpers = require('habitrpg-shared/script/helpers')
|
||||
|
||||
module.exports.app = (appExports, model, app) ->
|
||||
browser = require './browser'
|
||||
|
||||
_currentTime = model.at '_currentTime'
|
||||
_currentTime.setNull +new Date
|
||||
# Every 60 seconds, reset the current time so that the chat can update relative times
|
||||
setInterval (->_currentTime.set +new Date), 60000
|
||||
|
||||
appExports.groupCreate = (e,el) ->
|
||||
type = $(el).attr('data-type')
|
||||
newGroup =
|
||||
name: model.get("_new.group.name")
|
||||
description: model.get("_new.group.description")
|
||||
leader: user.get('id')
|
||||
members: [user.get('id')]
|
||||
type: type
|
||||
|
||||
# parties - free
|
||||
if type is 'party'
|
||||
return model.add 'groups', newGroup, ->location.reload()
|
||||
|
||||
# guilds - 4G
|
||||
unless user.get('balance') >= 1
|
||||
return $('#more-gems-modal').modal 'show'
|
||||
if confirm "Create Guild for 4 Gems?"
|
||||
newGroup.privacy = (model.get("_new.group.privacy") || 'public') if type is 'guild'
|
||||
newGroup.balance = 1 # they spent $ to open the guild, it goes into their guild bank
|
||||
model.add 'groups', newGroup, ->
|
||||
user.incr 'balance', -1, ->location.reload()
|
||||
|
||||
appExports.toggleGroupEdit = (e, el) ->
|
||||
path = "_editing.groups.#{$(el).attr('data-gid')}"
|
||||
model.set path, !model.get(path)
|
||||
|
||||
appExports.toggleLeaderMessageEdit = (e, el) ->
|
||||
path = "_editing.leaderMessage.#{$(el).attr('data-gid')}"
|
||||
model.set path, !model.get(path)
|
||||
|
||||
appExports.groupAddWebsite = (e, el) ->
|
||||
test = e.get()
|
||||
e.at().unshift 'websites', model.get('_newGroupWebsite')
|
||||
model.del '_newGroupWebsite'
|
||||
|
||||
appExports.groupInvite = (e,el) ->
|
||||
uid = model.get('_groupInvitee').replace(/[\s"]/g, '')
|
||||
model.set '_groupInvitee', ''
|
||||
return if _.isEmpty(uid)
|
||||
|
||||
model.query('users').publicInfo([uid]).fetch (err, profiles) ->
|
||||
throw err if err
|
||||
profile = profiles.at(0).get()
|
||||
return model.set("_groupError", "User with id #{uid} not found.") unless profile
|
||||
model.query('groups').withMember(uid).fetch (err, g) ->
|
||||
throw err if err
|
||||
group = e.get(); groups = g.get()
|
||||
{type, name} = group; gid = group.id
|
||||
groupError = (msg) -> model.set("_groupError", msg)
|
||||
invite = ->
|
||||
$.bootstrapGrowl "Invitation Sent."
|
||||
switch type
|
||||
when 'guild' then model.push "users.#{uid}.invitations.guilds", {id:gid, name}, ->location.reload()
|
||||
when 'party' then model.set "users.#{uid}.invitations.party", {id:gid, name}, ->location.reload()
|
||||
|
||||
switch type
|
||||
when 'guild'
|
||||
if profile.invitations?.guilds and _.find(profile.invitations.guilds, {id:gid})
|
||||
return groupError("User already invited to that group")
|
||||
else if uid in group.members
|
||||
return groupError("User already in that group")
|
||||
else invite()
|
||||
when 'party'
|
||||
if profile.invitations?.party
|
||||
return groupError("User already pending invitation.")
|
||||
else if _.find(groups, {type:'party'})
|
||||
return groupError("User already in a party.")
|
||||
else invite()
|
||||
|
||||
|
||||
appExports.acceptInvitation = (e,el) ->
|
||||
gid = e.get('id')
|
||||
if $(el).attr('data-type') is 'party'
|
||||
user.set 'invitations.party', null, ->joinGroup(gid)
|
||||
else
|
||||
e.at().remove ->joinGroup(gid)
|
||||
|
||||
appExports.rejectInvitation = (e, el) ->
|
||||
clear = -> browser.resetDom(model)
|
||||
if e.at().path().indexOf('party') != -1
|
||||
model.del e.at().path(), clear
|
||||
else e.at().remove clear
|
||||
|
||||
appExports.groupLeave = (e,el) ->
|
||||
if confirm("Leave this group, are you sure?") is true
|
||||
uid = user.get('id')
|
||||
group = model.at "groups.#{$(el).attr('data-id')}"
|
||||
index = group.get('members').indexOf(uid)
|
||||
if index != -1
|
||||
group.remove 'members', index, 1, ->
|
||||
updated = group.get()
|
||||
# last member out, delete the party
|
||||
if _.isEmpty(updated.members) and (updated.type is 'party')
|
||||
group.del ->location.reload()
|
||||
# assign new leader, so the party is editable #TODO allow old leader to assign new leader, this is just random
|
||||
else if (updated.leader is uid)
|
||||
group.set "leader", updated.members[0], ->location.reload()
|
||||
else location.reload()
|
||||
|
||||
###
|
||||
Chat Functionality
|
||||
###
|
||||
|
||||
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
|
||||
model.on 'unshift', '_habitrpg.chat', -> $('.chat-message').tooltip()
|
||||
|
||||
appExports.chatKeyup = (e, el, next) ->
|
||||
return next() unless e.keyCode is 13
|
||||
appExports.sendChat(e, el)
|
||||
|
||||
appExports.deleteChatMessage = (e) ->
|
||||
if confirm("Delete chat message?") is true
|
||||
e.at().remove() #requires the {#with}
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
$('#party-tab-link').on 'shown', (e) ->
|
||||
messages = model.get('_party.chat')
|
||||
return false unless messages?.length > 0
|
||||
model.set '_user.party.lastMessageSeen', messages[0].id
|
||||
|
||||
appExports.gotoPartyChat = ->
|
||||
model.set '_gamePane', true, ->
|
||||
$('#party-tab-link').tab('show')
|
||||
|
||||
appExports.assignGroupLeader = (e, el) ->
|
||||
newLeader = model.get('_new.groupLeader')
|
||||
if newLeader and (confirm("Assign new leader, you sure?") is true)
|
||||
e.at().set('leader', newLeader, ->browser.resetDom(model)) if newLeader
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
i18n = require 'derby-i18n'
|
||||
|
||||
i18n.plurals.add 'he', (n) -> n
|
||||
i18n.plurals.add 'bg', (n) -> n
|
||||
i18n.plurals.add 'nl', (n) -> n
|
||||
|
||||
module.exports = i18n
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# Translations
|
||||
i18n = require './i18n'
|
||||
i18n.localize app,
|
||||
availableLocales: ['en', 'he', 'bg', 'nl']
|
||||
defaultLocale: 'en'
|
||||
urlScheme: false
|
||||
checkHeader: true
|
||||
|
||||
|
||||
# ========== CONTROLLER FUNCTIONS ==========
|
||||
|
||||
ready (model) ->
|
||||
misc.fixCorruptUser(model) # https://github.com/lefnire/habitrpg/issues/634
|
||||
|
||||
# used for things like remove website, chat, etc
|
||||
exports.removeAt = (e, el) ->
|
||||
if (confirmMessage = $(el).attr 'data-confirm')?
|
||||
return unless confirm(confirmMessage) is true
|
||||
e.at().remove()
|
||||
browser.resetDom(model) if $(el).attr('data-refresh')
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
items = require 'habitrpg-shared/script/items'
|
||||
_ = require 'lodash'
|
||||
|
||||
updateStore = (model) ->
|
||||
nextItems = items.updateStore(model.get('_user'))
|
||||
_.each nextItems, (v,k) -> model.set("_items.next.#{k}",v); true
|
||||
|
||||
###
|
||||
server exports
|
||||
###
|
||||
module.exports.server = (model) ->
|
||||
model.set '_items', items.items
|
||||
updateStore(model)
|
||||
|
||||
###
|
||||
app exports
|
||||
###
|
||||
module.exports.app = (appExports, model) ->
|
||||
misc = require './misc'
|
||||
|
||||
model.on "set", "_user.items.*", -> updateStore(model)
|
||||
|
||||
appExports.buyItem = (e, el) ->
|
||||
misc.batchTxn model, (uObj, paths) ->
|
||||
ret = items.buyItem uObj, $(el).attr('data-type'), {paths}
|
||||
alert("Not enough GP") if ret is false
|
||||
|
||||
appExports.activateRewardsTab = ->
|
||||
model.set '_activeTabRewards', true
|
||||
model.set '_activeTabPets', false
|
||||
appExports.activatePetsTab = ->
|
||||
model.set '_activeTabPets', true
|
||||
model.set '_activeTabRewards', false
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
_ = require 'lodash'
|
||||
algos = require 'habitrpg-shared/script/algos'
|
||||
items = require('habitrpg-shared/script/items').items
|
||||
helpers = require('habitrpg-shared/script/helpers')
|
||||
|
||||
#TODO put this in habitrpg-shared
|
||||
###
|
||||
We can't always use refLists, but we often still need to get a positional path by id: eg, users.1234.tasks.5678.value
|
||||
For arrays (which use indexes, not id-paths), here's a helper function so we can run indexedPath('users',:user.id,'tasks',:task.id,'value)
|
||||
###
|
||||
indexedPath = ->
|
||||
_.reduce arguments, (m,v) =>
|
||||
return v if !m #first iteration
|
||||
return "#{m}.#{v}" if _.isString v #string paths
|
||||
return "#{m}." + _.findIndex(@model.get(m),v)
|
||||
, ''
|
||||
|
||||
taskInChallenge = (task) ->
|
||||
return undefined unless task?.challenge
|
||||
@model.at indexedPath.call(@, "groups.#{task.group.id}.challenges", {id:task.challenge}, "#{task.type}s", {id:task.id})
|
||||
|
||||
###
|
||||
algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the
|
||||
object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first
|
||||
clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination),
|
||||
perform the updates while tracking paths, then all the values at those paths
|
||||
###
|
||||
module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
||||
drop = undefined
|
||||
delta = batchTxn model, (uObj, paths) ->
|
||||
tObj = uObj.tasks[taskId]
|
||||
|
||||
# Stuff for undo
|
||||
if allowUndo
|
||||
tObjBefore = _.cloneDeep tObj
|
||||
tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo']
|
||||
previousUndo = model.get('_undo')
|
||||
clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId
|
||||
timeoutId = setTimeout (-> model.del('_undo')), 20000
|
||||
model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId}
|
||||
|
||||
delta = algos.score(uObj, tObj, direction, {paths})
|
||||
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
|
||||
drop = uObj._tmp?.drop
|
||||
|
||||
# Update challenge statistics
|
||||
# FIXME put this in it's own batchTxn, make batchTxn model.at() ref aware (not just _user)
|
||||
# FIXME use reflists for users & challenges
|
||||
if (chalTask = taskInChallenge.call({model}, tObj)) and chalTask?.get()
|
||||
model._dontPersist = false
|
||||
chalTask.incr "value", delta
|
||||
chal = model.at indexedPath.call({model}, "groups.#{tObj.group.id}.challenges", {id:tObj.challenge})
|
||||
chalUser = -> indexedPath.call({model}, chal.path(), 'users', {id:uObj.id})
|
||||
cu = model.at chalUser()
|
||||
unless cu?.get()
|
||||
chal.push "users", {id: uObj.id, name: helpers.username(uObj.auth, uObj.profile?.name)}
|
||||
cu = model.at chalUser()
|
||||
else
|
||||
cu.set 'name', helpers.username(uObj.auth, uObj.profile?.name) # update their name incase it changed
|
||||
cu.set "#{tObj.type}s.#{tObj.id}",
|
||||
value: tObj.value
|
||||
history: tObj.history
|
||||
model._dontPersist = true
|
||||
, done:->
|
||||
if drop and $?
|
||||
model.set '_drop', drop
|
||||
$('#item-dropped-modal').modal 'show'
|
||||
|
||||
delta
|
||||
|
||||
###
|
||||
Cleanup task-corruption (null tasks, rogue/invisible tasks, etc)
|
||||
Obviously none of this should be happening, but we'll stop-gap until we can find & fix
|
||||
Gotta love refLists! see https://github.com/lefnire/habitrpg/issues/803 & https://github.com/lefnire/habitrpg/issues/6343
|
||||
###
|
||||
module.exports.fixCorruptUser = (model) ->
|
||||
user = model.at('_user')
|
||||
tasks = user.get('tasks')
|
||||
|
||||
## Remove corrupted tasks
|
||||
_.each tasks, (task, key) ->
|
||||
unless task?.id? and task?.type?
|
||||
user.del("tasks.#{key}")
|
||||
delete tasks[key]
|
||||
true
|
||||
resetDom = false
|
||||
batchTxn model, (uObj, paths, batch) ->
|
||||
## fix https://github.com/lefnire/habitrpg/issues/1086
|
||||
uniqPets = _.uniq(uObj.items.pets)
|
||||
batch.set('items.pets', uniqPets) if !_.isEqual(uniqPets, uObj.items.pets)
|
||||
|
||||
if uObj.invitations?.guilds
|
||||
uniqInvites = _.uniq(uObj.invitations.guilds)
|
||||
batch.set('invitations.guilds', uniqInvites) if !_.isEqual(uniqInvites, uObj.invitations.guilds)
|
||||
|
||||
## Task List Cleanup
|
||||
['habit','daily','todo','reward'].forEach (type) ->
|
||||
|
||||
# 1. remove duplicates
|
||||
# 2. restore missing zombie tasks back into list
|
||||
idList = uObj["#{type}Ids"]
|
||||
taskIds = _.pluck( _.where(tasks, {type}), 'id')
|
||||
union = _.union idList, taskIds
|
||||
|
||||
# 2. remove empty (grey) tasks
|
||||
preened = _.filter union, (id) -> id and _.contains(taskIds, id)
|
||||
|
||||
# There were indeed issues found, set the new list
|
||||
if !_.isEqual(idList, preened)
|
||||
batch.set("#{type}Ids", preened)
|
||||
console.error uObj.id + "'s #{type}s were corrupt."
|
||||
true
|
||||
resetDom = !_.isEmpty(paths)
|
||||
require('./browser').resetDom(model) if resetDom
|
||||
|
||||
module.exports.viewHelpers = (view) ->
|
||||
|
||||
#misc
|
||||
view.fn "percent", (x, y) ->
|
||||
x=1 if x==0
|
||||
Math.round(x/y*100)
|
||||
view.fn 'indexOf', (str1, str2) ->
|
||||
return false unless str1 && str2
|
||||
str1.indexOf(str2) != -1
|
||||
view.fn "round", Math.round
|
||||
view.fn "floor", Math.floor
|
||||
view.fn "ceil", Math.ceil
|
||||
view.fn "truarr", (num) -> num-1
|
||||
view.fn 'count', (arr) -> arr?.length or 0
|
||||
view.fn 'int',
|
||||
get: (num) -> num
|
||||
set: (num) -> [parseInt(num)]
|
||||
view.fn 'indexedPath', indexedPath
|
||||
|
||||
|
||||
#iCal
|
||||
view.fn "encodeiCalLink", helpers.encodeiCalLink
|
||||
|
||||
#User
|
||||
view.fn "gems", (balance) -> balance * 4
|
||||
|
||||
#Challenges
|
||||
view.fn 'taskInChallenge', (task) ->
|
||||
taskInChallenge.call(@,task)?.get()
|
||||
view.fn 'taskAttrFromChallenge', (task, attr) ->
|
||||
taskInChallenge.call(@,task)?.get(attr)
|
||||
view.fn 'brokenChallengeLink', (task) ->
|
||||
task?.challenge and !(taskInChallenge.call(@,task)?.get())
|
||||
|
||||
view.fn 'challengeMemberScore', (member, tType, tid) ->
|
||||
Math.round(member["#{tType}s"]?[tid]?.value)
|
||||
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
_ = require 'lodash'
|
||||
{ randomVal } = require 'habitrpg-shared/script/helpers'
|
||||
{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items
|
||||
|
||||
###
|
||||
app exports
|
||||
###
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at '_user'
|
||||
|
||||
appExports.chooseEgg = (e, el) ->
|
||||
model.ref '_hatchEgg', e.at()
|
||||
|
||||
appExports.hatchEgg = (e, el) ->
|
||||
hatchingPotionName = $(el).children('select').val()
|
||||
myHatchingPotion = user.get 'items.hatchingPotions'
|
||||
egg = model.get '_hatchEgg'
|
||||
eggs = user.get 'items.eggs'
|
||||
myPets = user.get 'items.pets'
|
||||
|
||||
hatchingPotionIdx = myHatchingPotion.indexOf hatchingPotionName
|
||||
eggIdx = eggs.indexOf egg
|
||||
|
||||
return alert "You don't own that hatching potion yet, complete more tasks!" if hatchingPotionIdx is -1
|
||||
return alert "You don't own that egg yet, complete more tasks!" if eggIdx is -1
|
||||
return alert "You already have that pet, hatch a different combo." if myPets and myPets.indexOf("#{egg.name}-#{hatchingPotionName}") != -1
|
||||
|
||||
user.push 'items.pets', egg.name + '-' + hatchingPotionName, ->
|
||||
eggs.splice eggIdx, 1
|
||||
myHatchingPotion.splice hatchingPotionIdx, 1
|
||||
user.set 'items.eggs', eggs
|
||||
user.set 'items.hatchingPotions', myHatchingPotion
|
||||
|
||||
alert 'Your egg hatched! Visit your stable to equip your pet.'
|
||||
|
||||
#FIXME Bug: this removes from the array properly in the browser, but on refresh is has removed all items from the arrays
|
||||
# user.remove 'items.hatchingPotions', hatchingPotionIdx, 1
|
||||
# user.remove 'items.eggs', eggIdx, 1
|
||||
|
||||
appExports.choosePet = (e, el, next) ->
|
||||
petStr = $(el).attr('data-pet')
|
||||
|
||||
return next() if user.get('items.pets').indexOf(petStr) == -1
|
||||
# If user's pet is already active, deselect it
|
||||
return user.set 'items.currentPet', {} if user.get('items.currentPet.str') is petStr
|
||||
|
||||
[name, modifier] = petStr.split('-')
|
||||
pet = _.find pets, {name: name}
|
||||
pet.modifier = modifier
|
||||
pet.str = petStr
|
||||
user.set 'items.currentPet', pet
|
||||
|
||||
appExports.buyHatchingPotion = (e, el) ->
|
||||
name = $(el).attr 'data-hatchingPotion'
|
||||
newHatchingPotion = _.find hatchingPotions, {name: name}
|
||||
gems = user.get('balance') * 4
|
||||
if gems >= newHatchingPotion.value
|
||||
if confirm "Buy this hatching potion with #{newHatchingPotion.value} of your #{gems} Gems?"
|
||||
user.push 'items.hatchingPotions', newHatchingPotion.name
|
||||
user.set 'balance', (gems - newHatchingPotion.value) / 4
|
||||
else
|
||||
$('#more-gems-modal').modal 'show'
|
||||
|
||||
appExports.buyEgg = (e, el) ->
|
||||
name = $(el).attr 'data-egg'
|
||||
newEgg = _.find pets, {name: name}
|
||||
gems = user.get('balance') * 4
|
||||
if gems >= newEgg.value
|
||||
if confirm "Buy this egg with #{newEgg.value} of your #{gems} Gems?"
|
||||
user.push 'items.eggs', newEgg
|
||||
user.set 'balance', (gems - newEgg.value) / 4
|
||||
else
|
||||
$('#more-gems-modal').modal 'show'
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
algos = require 'habitrpg-shared/script/algos'
|
||||
helpers = require 'habitrpg-shared/script/helpers'
|
||||
_ = require 'lodash'
|
||||
moment = require 'moment'
|
||||
misc = require './misc'
|
||||
|
||||
appExports.clearCompleted = (e, el) ->
|
||||
completedIds = _.pluck( _.where(model.get('_todoList'), {completed:true}), 'id')
|
||||
todoIds = user.get('todoIds')
|
||||
_.each completedIds, (id) -> user.del "tasks.#{id}"; true
|
||||
user.set 'todoIds', _.difference(todoIds, completedIds)
|
||||
|
||||
|
||||
###
|
||||
Undo
|
||||
###
|
||||
appExports.undo = () ->
|
||||
undo = model.get '_undo'
|
||||
clearTimeout(undo.timeoutId) if undo?.timeoutId
|
||||
model.del '_undo'
|
||||
_.each undo.stats, (val, key) -> user.set "stats.#{key}", val; true
|
||||
taskPath = "tasks.#{undo.task.id}"
|
||||
_.each undo.task, (val, key) ->
|
||||
return true if key in ['id', 'type'] # strange bugs in this world: https://workflowy.com/shared/a53582ea-43d6-bcce-c719-e134f9bf71fd/
|
||||
if key is 'completed'
|
||||
user.pass({cron:true}).set("#{taskPath}.completed",val)
|
||||
else
|
||||
user.set "#{taskPath}.#{key}", val
|
||||
true
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
_ = require 'lodash'
|
||||
{ randomVal } = require 'habitrpg-shared/script/helpers'
|
||||
{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items
|
||||
|
||||
###
|
||||
Listeners to enabled flags, set notifications to the user when they've unlocked features
|
||||
###
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at('_user')
|
||||
|
||||
alreadyShown = (before, after) -> !(!before and after is true)
|
||||
|
||||
showPopover = (selector, title, html, placement='bottom') ->
|
||||
$(selector).popover('destroy')
|
||||
html += " <a href='#' onClick=\"$('#{selector}').popover('hide');return false;\">[Close]</a>"
|
||||
$(selector).popover({
|
||||
title: title
|
||||
placement: placement
|
||||
trigger: 'manual'
|
||||
html: true
|
||||
content: html
|
||||
}).popover 'show'
|
||||
|
||||
|
||||
user.on 'set', 'flags.customizationsNotification', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
$('.main-herobox').popover('destroy') #remove previous popovers
|
||||
html = "Click your avatar to customize your appearance."
|
||||
showPopover '.main-herobox', 'Customize Your Avatar', html, 'bottom'
|
||||
|
||||
user.on 'set', 'flags.itemsEnabled', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
html = """
|
||||
<img src='/vendor/BrowserQuest/client/img/1/chest.png' />
|
||||
Congratulations, you have unlocked the Item Store! You can now buy weapons, armor, potions, etc. Read each item's comment for more information.
|
||||
"""
|
||||
showPopover 'div.rewards', 'Item Store Unlocked', html, 'left'
|
||||
|
||||
user.on 'set', 'flags.petsEnabled', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
html = """
|
||||
<img src='/img/sprites/wolf_border.png' style='width:30px;height:30px;float:left;padding-right:5px' />
|
||||
You have unlocked Pets! You can now buy pets with Gems (note, you replenish Gems with real-life money - so chose your pets wisely!)
|
||||
"""
|
||||
showPopover '#rewardsTabs', 'Pets Unlocked', html, 'left'
|
||||
|
||||
user.on 'set', 'flags.partyEnabled', (after, before) ->
|
||||
return if user.get('party.current') or alreadyShown(before,after)
|
||||
html = """
|
||||
Be social, join a party and play Habit with your friends! You'll be better at your habits with accountability partners. Click User -> Options -> Party, and follow the instructions. LFG anyone?
|
||||
"""
|
||||
showPopover '.user-menu', 'Party System', html, 'bottom'
|
||||
|
||||
user.on 'set', 'flags.dropsEnabled', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
|
||||
egg = randomVal pets
|
||||
|
||||
dontPersist = model._dontPersist
|
||||
|
||||
model._dontPersist = false
|
||||
user.push 'items.eggs', egg
|
||||
model._dontPersist = dontPersist
|
||||
|
||||
$('#drops-enabled-modal').modal 'show'
|
||||
|
||||
user.on 'push', 'items.pets', (after, before) ->
|
||||
return if user.get('achievements.beastMaster')
|
||||
if before >= 90 # evidently before is the count?
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.beastMaster', true, (-> model._dontPersist = dontPersist)
|
||||
$('#beastmaster-achievement-modal').modal('show')
|
||||
|
||||
user.on 'set', 'items.*', (after, before) ->
|
||||
return if user.get('achievements.ultimateGear')
|
||||
items = user.get('items')
|
||||
if parseInt(items.weapon) >= 6 and parseInt(items.armor) >= 5 and parseInt(items.head) >= 5 and parseInt(items.shield) >= 5
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.ultimateGear', true, (->model._dontPersist = dontPersist)
|
||||
$('#max-gear-achievement-modal').modal('show')
|
||||
|
||||
user.on 'set', 'tasks.*.streak', (id, after, before) ->
|
||||
if after > 0
|
||||
|
||||
# 21-day streak, as per the old philosophy of doign a thing 21-days in a row makes a habit
|
||||
if (after % 21) is 0
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.incr 'achievements.streak', 1, (-> model._dontPersist = dontPersist)
|
||||
$('#streak-achievement-modal').modal('show')
|
||||
|
||||
# they're undoing a task at the 21 mark, take back their badge
|
||||
else if (before - after is 1) and (before % 21 is 0)
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.incr 'achievements.streak', -1, (-> model._dontPersist = dontPersist)
|
||||
|
|
@ -12,5 +12,5 @@ Shared resources useful for the multiple Habitica repositories, that way all the
|
|||
Because of some wonkiness with Heroku, the spritesheet compilation is not part of the production build process and must be done manually when new images are added by running:
|
||||
|
||||
``` bash
|
||||
grunt compile:sprites
|
||||
npm run sprites
|
||||
```
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue