try
|
|
@ -1,5 +0,0 @@
|
|||
.DS_Store
|
||||
public/gen/
|
||||
#lib/
|
||||
*.swp
|
||||
.idea/
|
||||
3
.bowerrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"directory": "public/bower_components"
|
||||
}
|
||||
12
.gitignore
vendored
|
|
@ -1,8 +1,16 @@
|
|||
.DS_Store
|
||||
public/gen
|
||||
node_modules
|
||||
#lib/
|
||||
*.swp
|
||||
.idea*
|
||||
config.json
|
||||
npm-debug.log
|
||||
npm-debug.log
|
||||
lib
|
||||
public/bower_components
|
||||
build
|
||||
|
||||
src/*/*.map
|
||||
src/*/*/*.map
|
||||
test/*.js
|
||||
test/*.map
|
||||
public/docs
|
||||
|
|
|
|||
9
.gitmodules
vendored
|
|
@ -1,9 +0,0 @@
|
|||
[submodule "public/vendor/github-buttons"]
|
||||
path = public/vendor/github-buttons
|
||||
url = https://github.com/mdo/github-buttons.git
|
||||
[submodule "public/vendor/BrowserQuest"]
|
||||
path = public/vendor/BrowserQuest
|
||||
url = https://github.com/mozilla/BrowserQuest.git
|
||||
[submodule "public/vendor/bootstrap-tour"]
|
||||
path = public/vendor/bootstrap-tour
|
||||
url = git://github.com/sorich87/bootstrap-tour.git
|
||||
5
.nodemonignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
public/*
|
||||
views/*
|
||||
build
|
||||
build/*
|
||||
Gruntfile.js
|
||||
8
.travis.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '0.10'
|
||||
before_script:
|
||||
- 'npm install -g bower grunt-cli'
|
||||
- 'bower install'
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
77
DOCS-README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# HabitRPG Docs Project
|
||||
|
||||
Generated documentation for all of HabitRPG's source files will be kept in the folder and subfolders. If you would like to use the existing documentation, or contribute to the documentation efforts, read on.
|
||||
|
||||
## Viewing Docs
|
||||
|
||||
You're looking at it!
|
||||
|
||||
Unless you are viewing this file directly from GitHub, you should see a list of files and folders to the left of this readme.
|
||||
|
||||
If you are working locally, you can goto `localhost:3000/docs/` and view the Docs.
|
||||
|
||||
All documentation is generated from comments in the code, into HTML files in the `public/docs/` folder. After you have cloned the HabitRPG repo locally, and done all the `npm install` goodness, the Docs should generate automagickly when you run `grunt run:dev`
|
||||
|
||||
## What I do now?
|
||||
|
||||
Well if you know Markdown, simply add detailed comments in the code using Markdown syntax.
|
||||
|
||||
````
|
||||
/*
|
||||
User.js
|
||||
=======
|
||||
|
||||
Defines the user data model (schema) for use via the API.
|
||||
*/
|
||||
|
||||
// Dependencies
|
||||
// ------------
|
||||
var mongoose = require("mongoose");
|
||||
var Schema = mongoose.Schema;
|
||||
var helpers = require('habitrpg-shared/script/helpers');
|
||||
var _ = require('lodash');....
|
||||
````
|
||||
|
||||
As you can see, you can use both multiline style comments `/* fancy stuff */` and inline comments `// Ooooh my`.
|
||||
|
||||
The exception being end of line comments
|
||||
`text: String, // example: Wolf `
|
||||
|
||||
The above will not be on the "pretty print" side of the Docs, but will stay in the code. An example use case for end of line comments would be for FIXME notes.
|
||||
|
||||
Add anything that would be helpful to a developer regarding how to use the functions, variables, and objects associated with HabitRPG.
|
||||
|
||||
**All documentation should be committed as pull request to the `docs-project` branch of HabitRPG.** Since we are adding comments directly to the code, I don't want to be editing files used for beta or master. We can merge in the docs after we're sure we didn't break anything.
|
||||
|
||||
### jsDoc Syntax
|
||||
|
||||
Yes, the generator also supports jsDoc-style comments such as
|
||||
````
|
||||
@param {Array} files Array of file paths relative to the `inDir` to generate documentation for.
|
||||
````
|
||||
|
||||
**Important Note:** If you use the `@param` syntax, you must use multiline comment blocks (ie `/* stuff */`), otherwise they won't be parsed like parameters.
|
||||
|
||||
This may or may not be useful for HabitRPG. Example use cases:
|
||||
- Documenting the API
|
||||
- Javascript Models
|
||||
|
||||
## Okay, I added great comments. Now what?
|
||||
|
||||
If you're running locally, just re-run `grunt run:dev`. Any changed docs will be automagickly updated.
|
||||
|
||||
Once you're satisfied with the output, push your changes to your fork of HabitRPG and issue a Pull Request on the `docs-project` branch.
|
||||
|
||||
It's that easy!
|
||||
|
||||
## Tech Info
|
||||
|
||||
The generator we are using is [Docker](https://github.com/jbt/docker), which is a fork of [Docco](http://jashkenas.github.io/docco/). Docker supports the same wide-range of filetypes, including being able to generate documentation for a whole project, including an index.
|
||||
|
||||
We also use the [Grunt-Docker](https://github.com/Prevole/grunt-docker) node module for automatic processing.
|
||||
|
||||
## Road Map
|
||||
|
||||
- Customize CSS with HabitRPG specific Styling
|
||||
- Explore possibilities of importing Wiki content
|
||||
- Specify style guide for consistency of comments
|
||||
123
Gruntfile.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*global module:false*/
|
||||
var _ = require('lodash');
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
|
||||
karma: {
|
||||
unit: {
|
||||
configFile: 'karma.conf.js'
|
||||
},
|
||||
continuous: {
|
||||
configFile: 'karma.conf.js',
|
||||
singleRun: true,
|
||||
autoWatch: false
|
||||
}
|
||||
},
|
||||
|
||||
clean: {
|
||||
build: ['build']
|
||||
},
|
||||
|
||||
stylus: {
|
||||
build: {
|
||||
options: {
|
||||
compress: false, // AFTER
|
||||
'include css': true,
|
||||
paths: ['public']
|
||||
},
|
||||
files: {
|
||||
'build/app.css': ['public/css/index.styl'],
|
||||
'build/static.css': ['public/css/static.styl']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy: {
|
||||
build: {
|
||||
files: [{expand: true, cwd: 'public/', src: 'favicon.ico', dest: 'build/'}]
|
||||
}
|
||||
},
|
||||
|
||||
// UPDATE IT WHEN YOU ADD SOME FILES NOT ALREADY MATCHED!
|
||||
hashres: {
|
||||
build: {
|
||||
options: {
|
||||
fileNameFormat: '${name}-${hash}.${ext}'
|
||||
},
|
||||
src: [
|
||||
'build/*.js', 'build/*.css', 'build/favicon.ico',
|
||||
'build/bower_components/bootstrap/docs/assets/css/*.css',
|
||||
'build/bower_components/habitrpg-shared/dist/*.css'
|
||||
],
|
||||
dest: 'make-sure-i-do-not-exist'
|
||||
}
|
||||
},
|
||||
|
||||
nodemon: {
|
||||
dev: {
|
||||
ignoredFiles: ['public/*', 'Gruntfile.js', 'views/*', 'build/*']
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
dev: {
|
||||
files: ['public/**/*.styl'], // 'public/**/*.js' Not needed because not in production
|
||||
tasks: [ 'build:dev' ],
|
||||
options: {
|
||||
nospawn: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
concurrent: {
|
||||
dev: ['nodemon', 'watch'],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//Load build files from public/manifest.json
|
||||
grunt.registerTask('loadManifestFiles', 'Load all build files from public/manifest.json', function(){
|
||||
var files = grunt.file.readJSON('./public/manifest.json');
|
||||
var uglify = {};
|
||||
var cssmin = {};
|
||||
_.each(files, function(val, key){
|
||||
var js = uglify['build/' + key + '.js'] = [];
|
||||
_.each(files[key]['js'], function(val){
|
||||
js.push('public/' + val);
|
||||
});
|
||||
_.each(files[key]['css'], function(val){
|
||||
if(val == 'app.css' || val == 'static.css'){
|
||||
cssmin['build/' + val] = ['build/' + val]
|
||||
}else{
|
||||
cssmin['build/' + val] = ['public/' + val]
|
||||
}
|
||||
});
|
||||
});
|
||||
grunt.config.set('uglify.build.files', uglify);
|
||||
grunt.config.set('cssmin.build.files', cssmin);
|
||||
});
|
||||
|
||||
// Register tasks.
|
||||
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
|
||||
grunt.registerTask('build:dev', ['loadManifestFiles', 'clean:build', 'stylus', 'cssmin', 'copy:build']);
|
||||
|
||||
grunt.registerTask('run:dev', [ 'build:dev', 'concurrent' ]);
|
||||
|
||||
// Load tasks
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-stylus');
|
||||
grunt.loadNpmTasks('grunt-contrib-cssmin');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-nodemon');
|
||||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-hashres');
|
||||
grunt.loadNpmTasks('grunt-karma');
|
||||
|
||||
};
|
||||
2
Procfile
|
|
@ -1 +1 @@
|
|||
web: node server.js
|
||||
web: ./node_modules/.bin/grunt build:prod;./node_modules/.bin/grunt nodemon;
|
||||
94
README.md
|
|
@ -1,17 +1,89 @@
|
|||
#[HabitRPG](http://habitrpg.com/)
|
||||
HabitRPG
|
||||
===============
|
||||
|
||||
HabitRPG is a 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.
|
||||
[HabitRPG](https://habitrpg.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.
|
||||
|
||||
[Read more](https://habitrpg.com/static/about)
|
||||
Built using Angular, Express, Mongoose, Jade, Stylus, Grunt and Bower.
|
||||
|
||||

|
||||
# Set up HabitRPG locally
|
||||
|
||||
##License
|
||||
Code is licensed under GNU GPL v3. Content is licensed under CC-BY-SA 3.0.
|
||||
See the LICENSE file for details.
|
||||
**Windows** users should skip this section and read the one below with Windows-specific steps.
|
||||
|
||||
##Credits
|
||||
Content comes from Mozilla's [BrowserQuest](http://browserquest.mozilla.org/)
|
||||
Before starting make sure to have [MongoDB](http://www.mongodb.org/), [NodeJS and npm](http://nodejs.org/) and [Git](https://help.github.com/articles/set-up-git) installed and set up.
|
||||
|
||||
* [Mozilla](http://mozilla.org)
|
||||
* [Little Workshop](http://www.littleworkshop.fr)
|
||||
1. [Fork the repository](https://help.github.com/articles/fork-a-repo) on your computer.
|
||||
1. Checkout the **develop** branch where all the development happens:
|
||||
`git checkout -b develop origin/develop`
|
||||
1. Install **grunt-cli** npm package globally (on some systems you may need to add `sudo` in front of the command below):
|
||||
`npm install -g grunt-cli bower`
|
||||
1. Install the **npm** and **bower** packages:
|
||||
`npm install`
|
||||
1. Create a config file from the example one:
|
||||
`cp config.json.example config.json`
|
||||
1. Ensure that Mongo is running and seed the database with initial settings by running:
|
||||
`node .\src\seed.js`.
|
||||
|
||||
## Windows Environment Install
|
||||
|
||||
1. Set up MongoDB. Steps:
|
||||
1. Download the latest production release of MongoDB from: http://www.mongodb.org/downloads
|
||||
1. Extract the zip file to the desired application directory. Example: c:\apps\mongodb-win32-x86_64-2.4.6
|
||||
1. Rename the folder from mongodb-win32-x86_64-2.4.6 to mongodb
|
||||
1. Create a data\db directory under the application directory. Example: c:\apps\mongodb\data\db
|
||||
1. Start up MongoDB using the following command:
|
||||
'c:\apps\mongodb\bin\mongod.exe --dbpath c:\apps\mongodb\data'
|
||||
|
||||
If MongoDB starts up successfully, you should see the following at the end of the logs:
|
||||
```Sun Sep 01 18:10:21.233 [initandlisten] waiting for connections on port 27017
|
||||
Sun Sep 01 18:10:21.233 [websvr] admin web console waiting for connections on po
|
||||
rt 28017```
|
||||
|
||||
1. Install Node.js (includes npm). Steps:
|
||||
1. Download and run the latest Node.js msi installation file from http://nodejs.org/download/
|
||||
1. Install [Git](https://help.github.com/articles/set-up-git).
|
||||
1. [Fork the repository](https://help.github.com/articles/fork-a-repo) on your computer.
|
||||
1. Checkout the **develop** branch where all the development happens:
|
||||
`git checkout -b develop origin/develop`
|
||||
1. Install the **npm** packages:
|
||||
`npm install`
|
||||
Read below for possible error message.
|
||||
|
||||
You might receive the following error during the 'npm install' command:
|
||||
> habitrpg@0.0.0-152 postinstall C:\Users\022498\Projects\habitrpg
|
||||
> ./node_modules/bower/bin/bower install -f
|
||||
'.' is not recognized as an internal or external command,
|
||||
operable program or batch file.
|
||||
npm ERR! weird error 1
|
||||
npm ERR! not ok code 0
|
||||
|
||||
Ignore this error and proceed with the following:
|
||||
|
||||
1. Install **grunt-cli** and **bower** npm packages globally
|
||||
'npm install -g grunt-cli bower'
|
||||
1. Install the **bower** packages:
|
||||
'bower install -f'
|
||||
1. Create a config file from the example one:
|
||||
`copy config.json.example config.json`
|
||||
1. Ensure that Mongo is running and seed the database with initial settings by r
|
||||
unning:
|
||||
`node ./src/seed.js`.
|
||||
|
||||
# Run HabitRPG
|
||||
|
||||
HabitRPG uses [Grunt](http://gruntjs.com) as its build tool.
|
||||
|
||||
`grunt run:dev` compiles the **Stylus** files and start a web server.
|
||||
|
||||
It uses [Nodemon](https://github.com/remy/nodemon) and [grunt-contrib-watch](https://github.com/gruntjs/grunt-contrib-watch) to automatically restart the server and re-compile the files when a change is detected.
|
||||
|
||||
**Open a browser to URL http://localhost:3000 to test the application!**
|
||||
|
||||
# Technologies discussion
|
||||
|
||||
1. Angular, Express, Mongoose. Awesome, tried technologies. Read up on them.
|
||||
1. Stylus, Jade - big debate.
|
||||
1. Jade. We need a server-side templating language so we can inject variables (`res.locals` from Express). Jade is great
|
||||
because the "significant whitespace" paradigm protects you from HTML errors such as missing or mal-matched close tags,
|
||||
which has been a pretty common error from multiple contribs on Habit. However, it's not very HTML-y, and makes people mad.
|
||||
We'll re-visit this conversation after the rewrite is done.
|
||||
1. Stylus. We're either staying here or moving to LESS, but vanilla CSS isn't cutting it for our app.
|
||||
|
|
|
|||
8
archive/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# 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!
|
||||
46
archive/derby_controllers/browser.coffee
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
_ = 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')
|
||||
|
||||
239
archive/derby_controllers/challenges.coffee
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
_ = 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')
|
||||
22
archive/derby_controllers/filters.coffee
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
_ = 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
|
||||
|
||||
139
archive/derby_controllers/groups.coffee
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
_ = 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
|
||||
20
archive/derby_controllers/index.coffee
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# 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')
|
||||
39
archive/derby_controllers/items.coffee
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
152
archive/derby_controllers/misc.coffee
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
_ = 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,6 +1,6 @@
|
|||
_ = require 'underscore'
|
||||
{ randomVal } = require './helpers'
|
||||
{ pets, hatchingPotions } = require('./items').items
|
||||
_ = require 'lodash'
|
||||
{ randomVal } = require 'habitrpg-shared/script/helpers'
|
||||
{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items
|
||||
|
||||
###
|
||||
app exports
|
||||
|
|
@ -25,12 +25,11 @@ module.exports.app = (appExports, model) ->
|
|||
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
|
||||
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.'
|
||||
|
||||
|
|
@ -46,14 +45,14 @@ module.exports.app = (appExports, model) ->
|
|||
return user.set 'items.currentPet', {} if user.get('items.currentPet.str') is petStr
|
||||
|
||||
[name, modifier] = petStr.split('-')
|
||||
pet = _.findWhere pets, name: name
|
||||
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 = _.findWhere hatchingPotions, name: name
|
||||
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?"
|
||||
|
|
@ -64,7 +63,7 @@ module.exports.app = (appExports, model) ->
|
|||
|
||||
appExports.buyEgg = (e, el) ->
|
||||
name = $(el).attr 'data-egg'
|
||||
newEgg = _.findWhere pets, name: name
|
||||
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?"
|
||||
29
archive/derby_controllers/tasks.coffee
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
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,6 +1,6 @@
|
|||
_ = require 'underscore'
|
||||
{ randomVal } = require './helpers'
|
||||
{ pets, hatchingPotions } = require('./items').items
|
||||
_ = 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
|
||||
|
|
@ -75,9 +75,9 @@ module.exports.app = (appExports, model) ->
|
|||
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)
|
||||
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) ->
|
||||
45
bower.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "HabitRPG",
|
||||
"version": "0.1.1",
|
||||
"homepage": "https://github.com/lefnire/habitrpg",
|
||||
"authors": [
|
||||
"Tyler Renelle <tylerrenelle@gmail.com>"
|
||||
],
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"public/bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": "~2.0.3",
|
||||
"jquery.cookie": "~1.4.0",
|
||||
"jquery-ui": "~1.10.3",
|
||||
"angular": "1.2.0-rc.3",
|
||||
"angular-sanitize": "1.2.0-rc.3",
|
||||
"angular-resource": "1.2.0-rc.3",
|
||||
"angular-ui": "~0.4.0",
|
||||
"angular-ui-utils": "~0.0.4",
|
||||
"angular-bootstrap": "~0.5.0",
|
||||
"angular-ui-router": "ca3b4777a603df8f86cfd653c8f6c38b2ae05d89",
|
||||
"angular-loading-bar": "~0.0.5",
|
||||
"bootstrap": "v2.3.2",
|
||||
"bootstrap-datepicker": "~1.2.0",
|
||||
"bootstrap-tour": "0.5.0",
|
||||
"bootstrap-growl": "~1.1.0",
|
||||
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git#master",
|
||||
"BrowserQuest": "https://github.com/mozilla/BrowserQuest.git",
|
||||
"github-buttons": "git://github.com/mdo/github-buttons.git",
|
||||
"marked": "~0.2.9",
|
||||
"JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~2.0.3",
|
||||
"bootstrap": "v2.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.2.0-rc.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,11 @@
|
|||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
||||
"NODE_ENV":"development",
|
||||
"SESSION_SECRET":"YOUR SECRET HERE",
|
||||
"ADMIN_EMAIL": "you@yours.com",
|
||||
"SMTP_USER":"user@domain.com",
|
||||
"SMTP_PASS":"password",
|
||||
"SMTP_SERVICE":"Gmail",
|
||||
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999"
|
||||
}
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
|
||||
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com"
|
||||
}
|
||||
|
|
|
|||
54
karma-e2e.conf.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Karma configuration
|
||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath: '',
|
||||
|
||||
// testing framework to use (jasmine/mocha/qunit/...)
|
||||
frameworks: ['ng-scenario'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'test/e2e/**/*.js'
|
||||
],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [],
|
||||
|
||||
// web server port
|
||||
port: 8080,
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['Chrome'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: false
|
||||
|
||||
// Uncomment the following lines if you are using grunt's server to run the tests
|
||||
// proxies: {
|
||||
// '/': 'http://localhost:9000/'
|
||||
// },
|
||||
// URL root prevent conflicts with the site root
|
||||
// urlRoot: '_karma_'
|
||||
});
|
||||
};
|
||||
57
karma.conf.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Karma configuration
|
||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath: '',
|
||||
|
||||
// testing framework to use (jasmine/mocha/qunit/...)
|
||||
frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'public/bower_components/angular/angular.js',
|
||||
'public/bower_components/angular-loading-bar/build/loading-bar.min.js',
|
||||
'public/bower_components/angular-resource/angular-resource.min.js',
|
||||
'public/bower_components/angular-mocks/angular-mocks.js',
|
||||
'public/bower_components/marked/lib/marked.js',
|
||||
'public/bower_components/habitrpg-shared/dist/habitrpg-shared.js',
|
||||
'public/js/*.js',
|
||||
'public/js/**/*.js',
|
||||
'test/mock/**/*.js',
|
||||
'test/spec/*.js',
|
||||
'test/spec/**/*.js'
|
||||
],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [],
|
||||
|
||||
// web server port
|
||||
port: 8080,
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['Firefox'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"tour4Title" : "Todos",
|
||||
"tour4Text" : "Todos are one-off goals which need to be completed eventually. ",
|
||||
"tour5Title" : "Rewards",
|
||||
"tour5Text" : "As you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integrals in forming good habits. ",
|
||||
"tour5Text" : "As you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integral in forming good habits. ",
|
||||
"tour6Title" : "Hover over comments",
|
||||
"tour6Text" : "Different task-types have special properties. Hover over each task's comment for more information. When you're ready to get started, delete the existing tasks and add your own.",
|
||||
|
||||
|
|
@ -71,6 +71,7 @@
|
|||
|
||||
"_commenttaskview": "TASK VIEW",
|
||||
"habits": "Habits",
|
||||
"Habits": "Habits",
|
||||
"newHabit": "New Habit",
|
||||
"edit": "Edit",
|
||||
"text": "Text",
|
||||
|
|
@ -87,6 +88,7 @@
|
|||
"progress": "Progress",
|
||||
"score": "Score",
|
||||
"dailies": "Dailies",
|
||||
"Dailies": "Dailies",
|
||||
"newDaily": "New Daily",
|
||||
"repeat": "Repeat",
|
||||
"Su": "Su",
|
||||
|
|
@ -97,11 +99,13 @@
|
|||
"F": "F",
|
||||
"S": "S",
|
||||
"todos": "Todos",
|
||||
"Todos": "Todos",
|
||||
"newTodo": "New Todo",
|
||||
"dueDate": "Due Date",
|
||||
"remaining": "Remaining",
|
||||
"complete": "Complete",
|
||||
"rewards": "Rewards",
|
||||
"Rewards": "Rewards",
|
||||
"gold": "Gold",
|
||||
"silver": "Silver",
|
||||
"newReward": "New Reward",
|
||||
|
|
@ -282,7 +286,7 @@
|
|||
"sheild1Text": "Decreases HP loss by 3%",
|
||||
"sheild2Name" : "Buckler",
|
||||
"sheild2Text": "Decreases HP loss by 4%",
|
||||
"sheild3Name" : "Enforced Shield",
|
||||
"sheild3Name" : "Reinforced Shield",
|
||||
"sheild3Text": "Decreases HP loss by 5%",
|
||||
"sheild4Name" : "Red Shield",
|
||||
"sheild4Text": "Decreases HP loss by 7%",
|
||||
|
|
@ -337,9 +341,15 @@
|
|||
"_commentnpcsandchars": "NPCS & CHARACTERS",
|
||||
"_commentNPCS" : "NPCS",
|
||||
"NPCBaileyText1" : "the Town Crier here! Announcing new stuff!",
|
||||
<<<<<<< HEAD
|
||||
"NPCAugustinText1" : "Welcome to the Market! I'm the merchant;",
|
||||
"NPCAugustinText2" : "Dying to get that particular pet you're after, but don't want to wait for it to drop? Buy it here!",
|
||||
"NPCJohanssonText1" : "Welcome to the Tavern! I'm",
|
||||
=======
|
||||
"NPCAugustinText1" : "Welcome to the Market! I'm the merchant;",
|
||||
"NPCAugustinText2" : "Dying to get that particular pet you're after, but don't want to wait for it to drop? Buy it here!",
|
||||
"NPCJohanssonText1" : "Welcome to the Tavern! I'm",
|
||||
>>>>>>> develop
|
||||
"NPCJohanssonText2": "the bar keep. If you want to rest a while (going on vacation? sudden illness?), I'll set you up at the inn - dailies won't hurt you while you're resting. Stay a while & meet the locals.",
|
||||
"NPCMelchiorText1" : "",
|
||||
"NPCBowenText1" : "",
|
||||
|
|
@ -379,7 +389,11 @@
|
|||
"_commentmisc": "MISC & GLOBAL",
|
||||
"removeAds": "Remove Ads",
|
||||
"whyAds": "Why Ads?",
|
||||
<<<<<<< HEAD
|
||||
"whyAdsContent1": "Habit 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.",
|
||||
=======
|
||||
"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.",
|
||||
>>>>>>> develop
|
||||
"whyAdsContent2": "'Hey, I backed the Kickstarter!' - follow",
|
||||
"whyAdsContent3": "these instructions",
|
||||
"_commentbuttons": "BUTTONS",
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* Set this up as a midnight cron script
|
||||
*
|
||||
* mongo habitrpg node_modules/moment/moment.js migrations/json.js migrations/20130212_preen_cron.js
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Users are allowed to experiment with the site before registering. Every time a new browser visits habitrpg, a new
|
||||
"staged" account is created - and if the user later registeres, that staged account is considered a "production" account.
|
||||
This function removes all staged accounts that have been abandoned - either older than a month, or corrupted in some way (lastCron==undefined)
|
||||
*/
|
||||
|
||||
var un_registered = {
|
||||
"auth.local": {$exists: false},
|
||||
"auth.facebook": {$exists: false}
|
||||
};
|
||||
var registered = {
|
||||
$or: [
|
||||
{ 'auth.local': { $exists: true }},
|
||||
{ 'auth.facebook': { $exists: true }}
|
||||
]
|
||||
};
|
||||
|
||||
var today = +(new Date);
|
||||
|
||||
// isValidDate = (d) ->
|
||||
// return false if Object::toString.call(d) isnt "[object Date]"
|
||||
// not isNaN(d.getTime())
|
||||
|
||||
|
||||
db.users.find(un_registered).forEach(function(user) {
|
||||
var diff, lastCron;
|
||||
if (!user) return;
|
||||
if (!!user.lastCron) {
|
||||
lastCron = new Date(user.lastCron);
|
||||
diff = Math.abs(moment(today).startOf('day').diff(moment(lastCron).startOf('day'), "days"));
|
||||
if (diff > 3) {
|
||||
return db.users.remove({_id:user._id});
|
||||
}
|
||||
} else {
|
||||
return db.users.update({_id: user._id}, {$set: {lastCron: today}});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Don't remove missing user auths anymore. This was previously necessary due to data corruption,
|
||||
* revisit if needs be
|
||||
*/
|
||||
/*db.sessions.find().forEach(function(sess){
|
||||
var uid = JSON.parse(sess.session).userId;
|
||||
if (!uid || db.users.count({_id:uid}) === 0) {
|
||||
db.sessions.remove({_id:sess._id});
|
||||
}
|
||||
});*/
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
|
||||
// since our primary subscription will first hit parties now, we *definitely* need an index there
|
||||
db.parties.ensureIndex( { 'members': 1, 'background': 1} );
|
||||
db.parties.ensureIndex( { 'members': 1}, {background: true} );
|
||||
|
||||
db.parties.find().forEach(function(party){
|
||||
|
||||
|
|
|
|||
48
migrations/20130518_setup_groups.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* In adding the Guilds feature (which supports the Challenges feature), we are consolidating parties and guilds
|
||||
* into one collection: groups, with group.type either 'party' or 'guild'. We are also creating the 'habitrpg' guild,
|
||||
* which everyone is auto-subscribed to, and moving tavern chat into that guild
|
||||
*
|
||||
* mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20130518_setup_groups.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 1) rename collection parties => groups
|
||||
* 2) add group.type = 'party' for each current group
|
||||
* 3) create habitrpg group, .type='guild'
|
||||
* 4) move tavern.chat.chat into habitrpg guild
|
||||
* 5) subscribe everyone to habitrpg (be sure to set that for default user too!)
|
||||
*/
|
||||
|
||||
db.parties.renameCollection('groups',true);
|
||||
//db.parties.dropCollection(); // doesn't seem to do this step during rename...
|
||||
//db.parties.ensureIndex( { 'members': 1, 'background': 1} );
|
||||
|
||||
db.groups.update({}, {$set:{type:'party'}}, {multi:true});
|
||||
|
||||
//migrate invitation mechanisms
|
||||
db.users.update(
|
||||
{},
|
||||
{
|
||||
$remove:{party:1},
|
||||
$set:{invitations:{party:null,guilds:[]}}
|
||||
},
|
||||
{multi:1}
|
||||
);
|
||||
|
||||
tavern = db.tavern.findOne();
|
||||
db.tavern.drop();
|
||||
|
||||
//TODO make as a callback of previous, or make sure group.type is still 'guild' for habitrpg in the end
|
||||
db.groups.insert({
|
||||
_id: "habitrpg",
|
||||
leader: '9',
|
||||
type: 'guild',
|
||||
name: "HabitRPG",
|
||||
chat: tavern.messages,
|
||||
info: {
|
||||
blurb: '',
|
||||
websites: []
|
||||
}
|
||||
});
|
||||
25
migrations/20130602_survey_rewards.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130602_survey_rewards.js
|
||||
|
||||
var members = []
|
||||
members = _.uniq(members);
|
||||
|
||||
var query = {
|
||||
_id: {$exists:1},
|
||||
$or:[
|
||||
{_id: {$in: members}},
|
||||
//{'profile.name': {$in: members}},
|
||||
{'auth.facebook.name': {$in: members}},
|
||||
{'auth.local.username': {$in: members}},
|
||||
{'auth.local.email': {$in: members}}
|
||||
]
|
||||
};
|
||||
|
||||
print(db.users.count(query));
|
||||
|
||||
db.users.update(query,
|
||||
{
|
||||
$set: { 'achievements.helpedHabit': true },
|
||||
$inc: { balance: 2.5 }
|
||||
},
|
||||
{multi:true}
|
||||
)
|
||||
9
migrations/20130612_survey_rewards_individual.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//mongo habitrpg migrations/20130612_survey_rewards_individual.js
|
||||
|
||||
var query = {_id: ""};
|
||||
|
||||
db.users.update(query,
|
||||
{
|
||||
$set: { 'achievements.helpedHabit': true },
|
||||
$inc: { balance: 2.5 }
|
||||
})
|
||||
4
migrations/20130615_add_extra_indexes.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
db.users.ensureIndex( { _id: 1, apiToken: 1 }, {background: true} )
|
||||
db.groups.ensureIndex( { members: 1 }, {background: true} )
|
||||
db.groups.ensureIndex( { type: 1 }, {background: true} )
|
||||
db.groups.ensureIndex( { type: 1, privacy: 1 }, {background: true} )
|
||||
16
migrations/20130908_cleanup_corrupt_tags.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130908_cleanup_corrupt_tags.js
|
||||
|
||||
// Racer was notorious for adding duplicates, randomly deleting documents, etc. Once we pull the plug on old.habit,
|
||||
// run this migration to cleanup all the corruption
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
user.tags = _.filter(user.tags, (function(t) {
|
||||
return !!t ? t.id : false;
|
||||
}));
|
||||
|
||||
try {
|
||||
db.users.update({_id:user._id}, {$set:{tags:user.tags}});
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
51
migrations/20130908_cleanup_derby_corruption.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130908_cleanup_derby_corruption.js
|
||||
|
||||
// Racer was notorious for adding duplicates, randomly deleting documents, etc. Once we pull the plug on old.habit,
|
||||
// run this migration to cleanup all the corruption
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
// remove corrupt tasks, which will either be null-value or no id
|
||||
user.tasks = _.reduce(user.tasks, function(m,task,k) {
|
||||
if (!task || !task.id) return m;
|
||||
if (isNaN(+task.value)) task.value = 0;
|
||||
m[k] = task;
|
||||
return m;
|
||||
}, {});
|
||||
|
||||
// fix NaN stats
|
||||
_.each(user.stats, function(v,k) {
|
||||
if (!v || isNaN(+v)) user.stats[k] = 0;
|
||||
return true;
|
||||
});
|
||||
|
||||
// remove duplicates, restore ghost tasks
|
||||
['habit', 'daily', 'todo', 'reward'].forEach(function(type) {
|
||||
var idList = user[type + "Ids"];
|
||||
var taskIds = _.pluck(_.where(user.tasks, {type: type}), 'id');
|
||||
var union = _.union(idList, taskIds);
|
||||
var preened = _.filter(union, function(id) {
|
||||
return id && _.contains(taskIds, id);
|
||||
});
|
||||
if (!_.isEqual(idList, preened)) {
|
||||
user[type + "Ids"] = preened;
|
||||
}
|
||||
});
|
||||
|
||||
// temporarily remove broken eggs. we'll need to write a migration script to grant gems for and remove these instead
|
||||
if (user.items && user.items.eggs) {
|
||||
user.items.eggs = _.filter(user.items.eggs,function(egg){
|
||||
if (_.isString(egg)) {
|
||||
user.balance += 0.75; // give them 3 gems for each broken egg
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
db.users.update({_id:user._id}, user);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
37
migrations/20130908_remove_staged_users.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Set this up as a midnight cron script
|
||||
*
|
||||
* mongo habitrpg ./migrations/20130908_remove_staged_users.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* If we experience any troubles with removed staging users, come back to a snapshot and restore accounts. This will
|
||||
* give a peak into possible conflict accounts:
|
||||
*/
|
||||
/*db.users.count({
|
||||
"auth.local": {$exists: false},
|
||||
"auth.facebook": {$exists: false},
|
||||
"history.exp.5": {$exists: 1},
|
||||
$where: "this.todoIds.length != 1 && this.dailyIds.length != 3 && this.habitIds.length != 3 && this.rewardIds.length != 2"
|
||||
})*/
|
||||
|
||||
/**
|
||||
* Users used to be allowed to experiment with the site before registering. Every time a new browser visits habitrpg, a new
|
||||
* "staged" account is created - and if the user later registers, that staged account is considered a "production" account.
|
||||
* This function removes all staged accounts, since the new site doesn't supported staged accounts, and when we add that feature
|
||||
* in we'll be using localStorage anyway instead of creating a new database record
|
||||
*/
|
||||
db.users.remove({
|
||||
// Un-registered users
|
||||
"auth.local": {$exists: false},
|
||||
"auth.facebook": {$exists: false}
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove empty parties
|
||||
* Another vestige of Racer. Empty parties shouldn't be being created anymore in the new site
|
||||
*/
|
||||
db.groups.remove({
|
||||
'type': 'party',
|
||||
$where: "return this.members.length === 0"
|
||||
});
|
||||
5
migrations/20131022_purchased_and_newStuff.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
db.users.find().forEach(function(user){
|
||||
if (!user.purchased) user.purchased = {hair: {}, skin: {}};
|
||||
user.purchased.ads = user.flags && !!user.flags.ads;
|
||||
db.users.update({_id:user._id}, {$set:{'purchased': user.purchased, 'flags.newStuff': true}, $unset: {'flags.ads':1}});
|
||||
});
|
||||
12
migrations/20131022_restore_ads.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// node .migrations/20131022_restore_ads.js
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
var dbBackup = mongo.db('localhost:27017/habitrpg?auto_reconnect');
|
||||
var dbLive = mongo.db('localhost:27017/habitrpg2?auto_reconnect');
|
||||
var count = 89474;
|
||||
dbBackup.collection('users').findEach({$or: [{'flags.ads':'show'}, {'flags.ads': null}]}, {batchSize:10}, function(err, item) {
|
||||
if (err) return console.error({err:err});
|
||||
if (!item || !item._id) return console.error('blank user');
|
||||
dbLive.collection('users').update({_id:item._id}, {$set:{'purchased.ads':false}, $unset: {'flags.ads': 1}});
|
||||
if (--count <= 0) console.log("DONE!");
|
||||
});
|
||||
126
migrations/20131028_task_subdocs_tags_invites.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20131028_task_subdocs_tags_invites.js
|
||||
|
||||
// TODO it might be better we just find() and save() all user objects using mongoose, and rely on our defined pre('save')
|
||||
// and default values to "migrate" users. This way we can make sure those parts are working properly too
|
||||
// @see http://stackoverflow.com/questions/14867697/mongoose-full-collection-scan
|
||||
//Also, what do we think of a Mongoose Migration module? something like https://github.com/madhums/mongoose-migrate
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
// Add invites to groups
|
||||
// -------------------------
|
||||
if(user.invitations){
|
||||
if(user.invitations.party){
|
||||
db.groups.update({_id: user.invitations.party.id}, {$addToSet:{invites:user._id}});
|
||||
}
|
||||
|
||||
if(user.invitations.guilds){
|
||||
_.each(user.invitations.guilds, function(guild){
|
||||
db.groups.update({_id: guild.id}, {$addToSet:{invites:user._id}});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup broken tags
|
||||
// -------------------------
|
||||
_.each(user.tasks, function(task){
|
||||
_.each(task.tags, function(val, key){
|
||||
_.each(user.tags, function(tag){
|
||||
if(key == tag.id) delete task.tags[key];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Fix corrupt dates
|
||||
// -------------------------
|
||||
user.lastCron = new Date(user.lastCron);
|
||||
if (user.lastCron == 'Invalid Date') user.lastCron = new Date();
|
||||
if (user.auth) { // what to do with !auth?
|
||||
_.defaults(user.auth, {timestamps: {created:undefined, loggedin: undefined}});
|
||||
_.defaults(user.auth.timestamps, {created: new Date(user.lastCron), loggedin: new Date(user.lastCron)});
|
||||
}
|
||||
|
||||
// Fix missing history
|
||||
// -------------------------
|
||||
_.defaults(user, {history:{}});
|
||||
_.defaults(user.history,{exp:[], todos:[]});
|
||||
|
||||
// Add username
|
||||
// -------------------------
|
||||
if (!user.profile) user.profile = {name:undefined};
|
||||
if (_.isEmpty(user.profile.name) && user.auth) {
|
||||
var fb = user.auth.facebook;
|
||||
user.profile.name =
|
||||
(user.auth.local && user.auth.local.username) ||
|
||||
(fb && (fb.displayName || fb.name || fb.username || (fb.first_name && fb.first_name + ' ' + fb.last_name))) ||
|
||||
'Anonymous';
|
||||
}
|
||||
|
||||
// Migrate to TaskSchema Sub-Docs!
|
||||
// -------------------------
|
||||
if (!user.tasks) {
|
||||
// So evidentaly users before 02/2013 were ALREADY setup based on habits[], dailys[], etcs... I don't remember our schema
|
||||
// ever being that way... Anyway, print busted users here (they don't have tasks, but also don't have the right schema)
|
||||
if (!user.habits || !user.dailys || !user.todos || !user.rewards) {
|
||||
print(user._id);
|
||||
}
|
||||
} else {
|
||||
_.each(['habit', 'daily', 'todo', 'reward'], function(type) {
|
||||
// we use _.transform instead of a simple _.where in order to maintain sort-order
|
||||
user[type + "s"] = _.reduce(user[type + "Ids"], function(m, tid) {
|
||||
var task = user.tasks[tid],
|
||||
newTask = {};
|
||||
if (!task) return m; // remove null tasks
|
||||
|
||||
// Cleanup tasks for TaskSchema
|
||||
newTask._id = newTask.id = task.id;
|
||||
newTask.text = (_.isString(task.text)) ? task.text : '';
|
||||
if (_.isString(task.notes)) newTask.notes = task.notes;
|
||||
newTask.tags = (_.isObject(task.tags)) ? task.tags : {};
|
||||
newTask.type = (_.isString(task.type)) ? task.type : 'habit';
|
||||
newTask.value = (_.isNumber(task.value)) ? task.value : 0;
|
||||
newTask.priority = (_.isString(task.priority)) ? task.priority : '!';
|
||||
switch (newTask.type) {
|
||||
case 'habit':
|
||||
newTask.up = (_.isBoolean(task.up)) ? task.up : true;
|
||||
newTask.down = (_.isBoolean(task.down)) ? task.down : true;
|
||||
newTask.history = (_.isArray(task.history)) ? task.history : [];
|
||||
break;
|
||||
case 'daily':
|
||||
newTask.repeat = (_.isObject(task.repeat)) ? task.repeat : {m:1, t:1, w:1, th:1, f:1, s:1, su:1};
|
||||
newTask.streak = (_.isNumber(task.streak)) ? task.streak : 0;
|
||||
newTask.completed = (_.isBoolean(task.completed)) ? task.completed : false;
|
||||
newTask.history = (_.isArray(task.history)) ? task.history : [];
|
||||
break;
|
||||
case 'todo':
|
||||
newTask.completed = (_.isBoolean(task.completed)) ? task.completed : false;
|
||||
break;
|
||||
}
|
||||
|
||||
m.push(newTask);
|
||||
return m;
|
||||
}, []);
|
||||
delete user[type + 'Ids'];
|
||||
});
|
||||
delete user.tasks;
|
||||
}
|
||||
|
||||
try {
|
||||
db.users.update({_id:user._id}, user);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove old groups.*.challenges, they're not compatible with the new system, set member counts
|
||||
// -------------------------
|
||||
db.groups.find().forEach(function(group){
|
||||
db.groups.update({_id:group._id}, {
|
||||
$set:{memberCount: _.size(group.members)},
|
||||
$pull:{challenges:1}
|
||||
})
|
||||
});
|
||||
|
||||
// HabitRPG => Tavern
|
||||
// -------------------------
|
||||
db.groups.update({_id:'habitrpg'}, {$set:{name:'Tavern'}});
|
||||
25
migrations/20131102_restore_task_ids.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20131028_task_subdocs_tags_invites.js
|
||||
|
||||
db.challenges.find().forEach(function(chal){
|
||||
_.each(chal.habits.concat(chal.dailys).concat(chal.todos).concat(chal.rewards), function(task){
|
||||
task.id = task.id || task._id;
|
||||
})
|
||||
try {
|
||||
db.challenges.update({_id:chal._id}, chal);
|
||||
db.groups.update({_id:chal.group}, {$addToSet:{challenges:chal._id}})
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
_.each(user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards), function(task){
|
||||
task.id = task.id || task._id;
|
||||
})
|
||||
try {
|
||||
db.users.update({_id:user._id}, user);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
|
||||
7
migrations/20131104_remove_invalid_dues.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
db.users.find({},{todos:1}).forEach(function(user){
|
||||
_.each(user.todos, function(task){
|
||||
if (moment(task.date).toDate() == 'Invalid Date')
|
||||
task.date = moment().format('MM/DD/YYYY');
|
||||
})
|
||||
db.users.update({_id:user._id}, {$set:{todos: user.todos}});
|
||||
});
|
||||
54
migrations/20131104_restore_lost_task_data.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// node .migrations/20131104_restore_lost_task_data.js
|
||||
|
||||
/**
|
||||
* After the great challenges migration, quite a few things got inadvertently dropped from tasks since their
|
||||
* schemas became more strict. See conversation at https://github.com/HabitRPG/habitrpg/issues/1712 ,
|
||||
* this restores task tags, streaks, due-dates, values
|
||||
*/
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
|
||||
var liveUsers = mongo.db('localhost:27017/habitrpg_new?auto_reconnect').collection('users');
|
||||
|
||||
backupUsers.count(function(err, count){
|
||||
if (err) return console.error(err);
|
||||
backupUsers.findEach({}, {batchSize:10}, function(err, before){
|
||||
if (err) return console.error(err);
|
||||
if (!before) return console.log('!before');
|
||||
liveUsers.findById(before._id, function(err, after){
|
||||
if (err) return console.error(err);
|
||||
if (!after) {
|
||||
count--;
|
||||
return console.log(before._id + ' deleted?');
|
||||
}
|
||||
if (before._id == '9') console.log('lefnire processed');
|
||||
_.each(before.tasks, function(tBefore){
|
||||
var tAfter = _.find(after[tBefore.type+'s'], {id:tBefore.id});
|
||||
if (!tAfter) return; // task has been deleted since launch
|
||||
|
||||
// Restore deleted tags
|
||||
if (!_.isEmpty(tBefore.tags) && _.isEmpty(tAfter.tags))
|
||||
tAfter.tags = tBefore.tags;
|
||||
// Except tags which are no longer available on the updated user
|
||||
_.each(tAfter.tags, function(v,k){ //value is true, key is tag.id
|
||||
if (!_.find(after.tags,{id:k})) delete tAfter.tags[k];
|
||||
})
|
||||
|
||||
// Restore deleted streaks
|
||||
if (+tBefore.streak > tAfter.streak)
|
||||
tAfter.streak = +tBefore.streak;
|
||||
|
||||
if (!!tBefore.date && !tAfter.date)
|
||||
tAfter.date = tBefore.date;
|
||||
|
||||
// Restore deleted values
|
||||
if (+tBefore.value != 0 && tAfter.value == 0)
|
||||
tAfter.value = +tBefore.value;
|
||||
})
|
||||
after._v++;
|
||||
liveUsers.update({_id:after._id}, after);
|
||||
if (--count <= 0) console.log("DONE!");
|
||||
})
|
||||
});
|
||||
});
|
||||
21
migrations/20131105_remove_history_ids.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
function deleteId(h){
|
||||
delete h._id;
|
||||
}
|
||||
|
||||
db.users.find({},{habits:1,dailys:1,history:1}).forEach(function(user){
|
||||
if (user.history) {
|
||||
_.each(['todos','exp'], function(type){
|
||||
if (user.history[type]) {
|
||||
_.each(user.history.exp, deleteId);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
user.history = {exp:[],todos:[]};
|
||||
}
|
||||
|
||||
_.each(['habits', 'dailys'], function(type){
|
||||
_.each(user[type].history, deleteId);
|
||||
});
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{history: user.history, habits: user.habits, dailys: user.dailys}});
|
||||
});
|
||||
18
migrations/20131107_from_backer_to_contributor.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
db.users.find({
|
||||
$or: [
|
||||
{'backer.admin':{$exists:1}},
|
||||
{'backer.contributor':{$exists:1}}
|
||||
]
|
||||
},{backer:1}).forEach(function(user){
|
||||
user.contributor = {};
|
||||
user.contributor.admin = user.backer.admin;
|
||||
delete user.backer.admin;
|
||||
|
||||
// this isnt' the proper storage format, but I'm going to be going through the admin utility manually and setting things properly
|
||||
if (user.backer.contributor) {
|
||||
user.contributor.text = user.backer.contributor;
|
||||
delete user.backer.contributor;
|
||||
}
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{backer:user.backer, contributor:user.contributor}});
|
||||
});
|
||||
4
migrations/20131108_add_gems_for_contribs.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Increase everyone's gems per their contribution level
|
||||
db.users.find({'contributor.level':{$gt:0}},{contributor:1, balance:1}).forEach(function(user){
|
||||
db.users.update({_id:user._id}, {$inc: {balance: (user.contributor.level * .5)} });
|
||||
});
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import csv
|
||||
|
||||
data = csv.reader(open('/home/slappybag/backrs/800dollar.csv', 'rb'), delimiter=",", quotechar='|')
|
||||
column = []
|
||||
with open(r"/home/slappybag/Documents/SurveyScrape.csv") as f:
|
||||
reader = csv.reader(f, delimiter=',', quotechar='"')
|
||||
column = []
|
||||
for row in reader:
|
||||
if row:
|
||||
column.append(row[4])
|
||||
|
||||
for row in data:
|
||||
column.append(row[9])
|
||||
|
||||
print "one:"
|
||||
print column
|
||||
10
migrations/facebook_to_local.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
var oldId = "",
|
||||
newId = "",
|
||||
newUser = db.users.findOne({_id: newId})
|
||||
|
||||
db.users.update({_id: oldId}, {$set:{auth: newUser.auth}});
|
||||
|
||||
// remove the auth on the new user (which is a template account). The account will be preened automatically later,
|
||||
// this allows us to keep the account around a few days in case there was a mistake
|
||||
db.users.update({_id: newId}, {$unset:{auth:1}});
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/find_unique_user.js
|
||||
// mongo habitrpg ./node_modules/lodash/index.js ./migrations/find_unique_user.js
|
||||
|
||||
/**
|
||||
* There are some rare instances of lost user accounts, due to a corrupt user auth variable (see https://github.com/lefnire/habitrpg/wiki/User-ID)
|
||||
|
|
@ -6,6 +6,6 @@
|
|||
*/
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
var found = _.findWhere(user.tasks, {text: "Replace Me"})
|
||||
var found = _.any(user.tasks, {text: "Replace Me"})
|
||||
if (found) printjson({id:user._id, auth:user.auth});
|
||||
})
|
||||
86
migrations/metrics.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// node habitrpg ./migrations/metrics.js
|
||||
|
||||
var EXPORT_EMAILS = true;
|
||||
var mongo = require('mongoskin');
|
||||
var csv = require('csv');
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
var db = mongo.db('localhost:27017/habitrpg2?auto_reconnect');
|
||||
var twoWeeksAgo = moment().subtract(14, 'days');
|
||||
var angularRewrite = moment('07/09/2013');
|
||||
|
||||
var query = {auth: {'$exists':1}};
|
||||
var fields = {lastCron:1, 'history.exp':1, 'auth.timestamps':1, 'auth.local.email':1};
|
||||
db.collection('users').find(query, fields).toArray(function(err, items) {
|
||||
if (err) return console.error({err:err});
|
||||
var stats = {total: _.size(items), lostToDerby: 0, isActive: 0};
|
||||
var emails = [];
|
||||
_.each(items, function(item) {
|
||||
//if (!item.history || !item.history.exp) console.log(item._id)
|
||||
|
||||
//var hasBeenActive = item.history && item.history.exp && item.history.exp.length > 7;
|
||||
var hasBeenActive = item.auth.timestamps && item.auth.timestamps.created &&
|
||||
(Math.abs(moment(item.lastCron).diff(item.auth.timestamps.created, 'd')) > 14);
|
||||
|
||||
if (/*hasBeenActive && */moment(item.lastCron).isBefore(angularRewrite)) {
|
||||
stats.lostToDerby++;
|
||||
if (item.auth.local)
|
||||
emails.push([item.auth.local.email]);
|
||||
// Facebook emails. Kinda dirty, and there's only ~30 available fb emails anyway.
|
||||
// } else if (item.auth.facebook && item.auth.facebook.email) {
|
||||
// emails.push([item.auth.facebook.email])
|
||||
// } else if (item.auth.facebook && item.auth.facebook.emails && item.auth.facebook.emails[0] && !!item.auth.facebook.emails[0].value) {
|
||||
// emails.push([item.auth.facebook.emails[0].value])
|
||||
}
|
||||
if (hasBeenActive && moment(item.lastCron).isAfter(twoWeeksAgo)) {
|
||||
stats.isActive++;
|
||||
}
|
||||
|
||||
})
|
||||
stats.emails = _.size(emails);
|
||||
console.log(stats);
|
||||
|
||||
if (EXPORT_EMAILS)
|
||||
csv().from.array(emails).to.path(__dirname+'/emails.csv')
|
||||
});
|
||||
|
||||
/*
|
||||
load('./node_modules/moment/moment.js');
|
||||
var today = +new Date,
|
||||
twoWeeksAgo = +moment().subtract(14, 'days');
|
||||
|
||||
corrupt = {
|
||||
$or: [
|
||||
{lastCron: {$exists:false}},
|
||||
{lastCron: 'new'}
|
||||
]
|
||||
}
|
||||
|
||||
un_registered = {
|
||||
"auth.local": {$exists: false},
|
||||
"auth.facebook": {$exists: false}
|
||||
},
|
||||
|
||||
registered = {
|
||||
$or: [
|
||||
{ 'auth.local': { $exists: true }},
|
||||
{ 'auth.facebook': { $exists: true }}
|
||||
]
|
||||
},
|
||||
|
||||
active = {
|
||||
$or: [
|
||||
{ 'auth.local': { $exists: true }},
|
||||
{ 'auth.facebook': { $exists: true }}
|
||||
],
|
||||
$where: function(){
|
||||
return this.history && this.history.exp && this.history.exp.length > 7;
|
||||
},
|
||||
'lastCron': {$gt: twoWeeksAgo}
|
||||
};
|
||||
|
||||
print('corrupt: ' + db.users.count(corrupt));
|
||||
print('unregistered: ' + db.users.count(un_registered));
|
||||
print('registered: ' + db.users.count(registered));
|
||||
print('active: ' + db.users.count(active));
|
||||
*/
|
||||
|
|
@ -1 +1 @@
|
|||
db.users.update({},{$set:{'flags.newStuff':'show'}},{multi:true})
|
||||
db.users.update({},{$set:{'flags.newStuff':true}},{multi:true})
|
||||
69
package.json
|
|
@ -1,32 +1,48 @@
|
|||
{
|
||||
"name": "habitrpg",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "0.0.0-151",
|
||||
"main": "./server.js",
|
||||
"version": "0.0.0-152",
|
||||
"main": "./src/server.js",
|
||||
"dependencies": {
|
||||
"derby": "git://github.com/lefnire/derby#habitrpg",
|
||||
"racer": "git://github.com/lefnire/racer#habitrpg",
|
||||
"racer-db-mongo": "git://github.com/lefnire/racer-db-mongo#habitrpg",
|
||||
"derby-ui-boot": "git://github.com/codeparty/derby-ui-boot#master",
|
||||
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#master",
|
||||
"derby-auth": "git://github.com/lefnire/derby-auth#master",
|
||||
"connect-mongo": "*",
|
||||
"passport-facebook": "*",
|
||||
"passport-facebook": "~1.0.0",
|
||||
"express": "*",
|
||||
"gzippo": "*",
|
||||
"guid": "*",
|
||||
"moment": "*",
|
||||
"stripe": "*",
|
||||
"lodash": "1.0.x",
|
||||
"coffee-script": "1.4.x",
|
||||
"underscore": "*",
|
||||
"mongoskin": "*",
|
||||
"coffee-script": "*",
|
||||
"nconf": "*",
|
||||
"icalendar": "git://github.com/lefnire/node-icalendar#master",
|
||||
"superagent": "~0.12.4",
|
||||
"resolve": "~0.2.3",
|
||||
"expect.js": "~0.2.0",
|
||||
"derby-i18n": "git://github.com/switz/derby-i18n#master",
|
||||
"relative-date": "~1.1.1"
|
||||
"relative-date": "~1.1.1",
|
||||
"lodash": "~2.2.1",
|
||||
"async": "~0.2.9",
|
||||
"optimist": "~0.5.2",
|
||||
"mongoose": "~3.6.20",
|
||||
"stylus": "~0.37.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-contrib-uglify": "~0.2.4",
|
||||
"grunt-contrib-stylus": "~0.8.0",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-cssmin": "~0.6.1",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-hashres": "~0.3.2",
|
||||
"grunt-nodemon": "~0.1.1",
|
||||
"grunt-concurrent": "~0.3.1",
|
||||
"bower": "~1.2.4",
|
||||
"nib": "~1.0.1",
|
||||
"jade": "~0.35.0",
|
||||
"passport": "~0.1.17",
|
||||
"validator": "~1.5.1",
|
||||
"nodemailer": "~0.5.2",
|
||||
"grunt-cli": "~0.1.9",
|
||||
"paypal-ipn": "~1.0.1"
|
||||
},
|
||||
"private": true,
|
||||
"subdomain": "habitrpg",
|
||||
|
|
@ -35,11 +51,30 @@
|
|||
"www.habitrpg.com"
|
||||
],
|
||||
"engines": {
|
||||
"node": "0.8.x",
|
||||
"npm": "1.1.x"
|
||||
"node": "0.10.x",
|
||||
"npm": "1.2.x"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "server.js",
|
||||
"test": "mocha test/api.mocha.coffee"
|
||||
"test": "grunt karma:continuous",
|
||||
"start": "grunt run:dev",
|
||||
"postinstall": "./node_modules/bower/bin/bower install -f"
|
||||
},
|
||||
"devDependencies": {
|
||||
"karma-ng-scenario": "~0.1.0",
|
||||
"grunt-karma": "~0.6.2",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"karma-chrome-launcher": "~0.1.0",
|
||||
"karma-firefox-launcher": "~0.1.0",
|
||||
"karma-html2js-preprocessor": "~0.1.0",
|
||||
"karma-coffee-preprocessor": "~0.1.0",
|
||||
"karma-jasmine": "~0.1.3",
|
||||
"karma-requirejs": "~0.1.0",
|
||||
"karma-phantomjs-launcher": "~0.1.0",
|
||||
"karma": "~0.10.2",
|
||||
"karma-ng-html2js-preprocessor": "~0.1.0",
|
||||
"karma-chai-plugins": "~0.1.0",
|
||||
"mocha": "~1.12.1",
|
||||
"karma-mocha": "~0.1.0",
|
||||
"csv": "~0.3.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<link href="/vendor/bootstrap/docs/assets/css/docs.css" rel="stylesheet">
|
||||
<link href="/css/static-pages.css" rel="stylesheet">
|
||||
|
||||
<style type="text/css">
|
||||
<!-- <style type="text/css">
|
||||
.rotate-img {
|
||||
transform:rotate(90deg);
|
||||
-ms-transform:rotate(90deg); /* IE 9 */
|
||||
|
|
@ -19,15 +19,17 @@
|
|||
-webkit-transform:rotate(90deg); /* Safari and Chrome */
|
||||
-o-transform:rotate(90deg); /* Opera */
|
||||
}
|
||||
</style>
|
||||
</style> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='container'>
|
||||
<div class='marketing'>
|
||||
<img class='rotate-img' src="/img/sprites/armor3_m.png" />
|
||||
<h2>The server is experiencing issues.</h2>
|
||||
<p><a href="/">Try again</a> in a few, the developer has been notified. The most likely culprit is <a href="https://github.com/lefnire/habitrpg/issues/165">this issue</a> which Tyler is working to fix. (Any memory leak experts?)</p>
|
||||
<img src="/img/sprites/dead.png" />
|
||||
<h2>The server is respawning.</h2>
|
||||
<p><a href="/">Try again</a> in a few. We restart often due to <a href="https://github.com/lefnire/habitrpg/issues/165">this issue</a>, and we're <a href=http://habitrpg.tumblr.com/post/55655159428/0-5-upgrade-aborted-angularjs-future>rewriting the site</a> to fix it. (AngularJS developers, come <a href=https://github.com/lefnire/habitrpg/tree/angular_rewrite>join us</a>!)</p>
|
||||
|
||||
<p>If this page persists, the server may be experiencing issues; the developers have been notified. Try switching to <a href="https://beta.habitrpg.com/">the beta site</a> or <a href="https://habitrpg.com/">the main site</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
|||
1
public/css/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
We need to extract any sprite-sheet css in this directory (anything that's not habitrpg-web specific) and move it to habitrpg-shared
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
.new-stuff
|
||||
position: fixed
|
||||
text-align: center
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
height: 0
|
||||
z-index: 1010
|
||||
position: fixed
|
||||
text-align: center
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
height: 0
|
||||
z-index: 1010
|
||||
|
||||
.new-stuff> .alert
|
||||
border-top: 0
|
||||
border-radius: 0 0 4px 4px
|
||||
padding-right: 14px
|
||||
display: inline-block
|
||||
border-top: 0
|
||||
border-radius: 0 0 4px 4px
|
||||
padding-right: 14px
|
||||
display: inline-block
|
||||
|
||||
.wide-popover
|
||||
max-width: 400px
|
||||
|
||||
// variables
|
||||
|
||||
|
|
@ -58,21 +60,21 @@ borderDarken = 20%
|
|||
// alert icons
|
||||
|
||||
.icon-gold
|
||||
background: url("img/coin_single_gold.png") no-repeat
|
||||
background: url("/bower_components/habitrpg-shared/img/coin_single_gold.png") no-repeat
|
||||
background-position: center center
|
||||
background-size: 18px
|
||||
width: 14px
|
||||
height: 14px
|
||||
|
||||
.icon-silver
|
||||
background: url("img/coin_single_silver.png") no-repeat
|
||||
background: url("/bower_components/habitrpg-shared/img/coin_single_silver.png") no-repeat
|
||||
background-position: center center
|
||||
background-size: 18px
|
||||
width: 14px
|
||||
height: 14px
|
||||
|
||||
.icon-death
|
||||
background: url("img/sprites/dead.png") no-repeat
|
||||
background: url("/bower_components/habitrpg-shared/img/sprites/dead.png") no-repeat
|
||||
background-position: center center
|
||||
background-size: 14px
|
||||
width: 14px
|
||||
|
|
@ -83,18 +85,4 @@ borderDarken = 20%
|
|||
z-index: 3000
|
||||
position:absolute
|
||||
left:5px
|
||||
top:5px
|
||||
|
||||
// Bailey
|
||||
|
||||
.rewards-module
|
||||
z-index:2000
|
||||
|
||||
.hiding-bailey
|
||||
cursor: pointer
|
||||
position:absolute
|
||||
right: 50px
|
||||
top: 117px //FIXME this isn't a good method, but i suck at css - tyler
|
||||
|
||||
.new-stuff-word-bubble-container
|
||||
height: 90px
|
||||
top:5px
|
||||
|
|
@ -40,7 +40,7 @@ future re: pets and whatnot, this is just temporary.
|
|||
// always visible on the user's own avatar
|
||||
.herobox:after
|
||||
opacity: 0
|
||||
.herobox[data-checkuser="isUser"]:after
|
||||
.herobox.isUser:after
|
||||
opacity: 1
|
||||
// make it a bit special
|
||||
background darken($better, 15%)
|
||||
|
|
@ -54,18 +54,18 @@ future re: pets and whatnot, this is just temporary.
|
|||
|
||||
// vertically center avatar sprite
|
||||
// depending on if they have a pet
|
||||
.herobox[data-checkpet="hasPet"]
|
||||
.herobox.hasPet
|
||||
padding-top: 2em
|
||||
.herobox:not([data-checkpet="hasPet"])
|
||||
.herobox:not(.hasPet)
|
||||
padding-top: 1.75em
|
||||
|
||||
// if it is the user's avatar, check if
|
||||
// they have a pet and pad accordingly
|
||||
.herobox[data-checkpet="hasPet"][data-checkuser="isUser"]
|
||||
.herobox.hasPet.isUser
|
||||
padding-top: 3.25em
|
||||
|
||||
// if not user's avatar, remove lines for party unity
|
||||
.herobox:not([data-checkuser="isUser"])
|
||||
.herobox:not(.isUser)
|
||||
outline: 0
|
||||
|
||||
// expose hero info on hover, taking
|
||||
|
|
@ -74,10 +74,10 @@ future re: pets and whatnot, this is just temporary.
|
|||
background: desaturate(lighten($better, 30%), 10%)
|
||||
&:after
|
||||
opacity: 1
|
||||
.herobox[data-checkpet="hasPet"]
|
||||
.herobox.hasPet
|
||||
&:hover, &:focus
|
||||
padding-top: 3.25em
|
||||
.herobox:not([data-checkpet="hasPet"])
|
||||
.herobox:not(.hasPet)
|
||||
&:hover, &:focus
|
||||
padding-top: 2.5em
|
||||
|
||||
21
public/css/challenges.styl
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
ul.challenge-accordion-header-specs
|
||||
list-style:none
|
||||
|
||||
li
|
||||
background-color: darken($neutral, 10%)
|
||||
margin: 2px 5px
|
||||
float:left
|
||||
|
||||
#create-challenge-btn
|
||||
margin-bottom: 10px
|
||||
|
||||
#challenges-filters h3
|
||||
margin-top: 0px;
|
||||
|
||||
// for things like challenges, member profiles, etc
|
||||
@media (min-width: 767px)
|
||||
.wide-modal
|
||||
/* new custom width */
|
||||
width: 1020px;
|
||||
/* must be half of the width, minus scrollbar on the left (30px) */
|
||||
margin-left: -480px;
|
||||
|
|
@ -50,4 +50,8 @@ menu
|
|||
.customize-menu {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.well.limited-edition
|
||||
padding: 5px
|
||||
margin: 0px
|
||||
1067
public/css/docs.css
60
public/css/game-pane.styl
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
.full-width
|
||||
width 100%
|
||||
|
||||
.border-right
|
||||
border-right 1px solid #ddd
|
||||
|
||||
.tab-notification
|
||||
color #fff
|
||||
background-color #51a351
|
||||
|
||||
.tavern-pane
|
||||
position relative
|
||||
height 250px
|
||||
|
||||
.tavern-chat, .party-chat
|
||||
|
||||
li
|
||||
padding-top:15px
|
||||
padding-bottom:15px
|
||||
border-bottom 1px solid #ddd
|
||||
word-wrap:break-word
|
||||
|
||||
&.highlight
|
||||
background #EEE
|
||||
|
||||
label
|
||||
margin-right:5px
|
||||
|
||||
.own-message
|
||||
border-left: 4px solid #333
|
||||
padding-left: 2px
|
||||
|
||||
// Name tags
|
||||
.label-contributor-1, .label-contributor-2
|
||||
background-color: #333;
|
||||
.label-contributor-3, .label-contributor-4
|
||||
background-color: #077409
|
||||
color: white
|
||||
.label-contributor-5, .label-contributor-6
|
||||
background-color: #125BA2
|
||||
color: white
|
||||
.label-contributor-7
|
||||
background-color: #7313B4
|
||||
color: white
|
||||
.label-contributor-8
|
||||
background-color: #ff8000
|
||||
color: white
|
||||
.label-npc
|
||||
background-color: indianred
|
||||
color: white
|
||||
|
||||
#market-tab
|
||||
position relative
|
||||
height 500px
|
||||
|
||||
.buttonList li
|
||||
margin: 5px
|
||||
|
||||
.option-group .option-time
|
||||
padding: 0px 5px
|
||||
|
|
@ -7,7 +7,9 @@
|
|||
background: #f5f5f5
|
||||
border-bottom: 1px solid rgba(0,0,0,0.2)
|
||||
// margin-top: -1px
|
||||
overflow: hidden
|
||||
overflow-y: hidden
|
||||
overflow-x: auto
|
||||
// position relative
|
||||
|
||||
/* login/menu buttons
|
||||
--------------------- */
|
||||
|
|
@ -16,6 +18,11 @@
|
|||
top: 0.5em
|
||||
right: 0.5em
|
||||
font-size: 0.85em
|
||||
z-index: 1011
|
||||
|
||||
.site-nav
|
||||
margin-bottom: 0px
|
||||
|
||||
.tile
|
||||
cursor: pointer
|
||||
font-weight: 400
|
||||
|
|
@ -1,30 +1,28 @@
|
|||
@import "nib/vendor";
|
||||
@import "nib/vendor"
|
||||
|
||||
// Vendor Includes - include first so we can override
|
||||
@import "../../public/vendor/bootstrap/css/bootstrap.min.css";
|
||||
@import "../../public/vendor/bootstrap/css/bootstrap-responsive.min.css";
|
||||
@import "../../public/vendor/datepicker/css/datepicker.css";
|
||||
// Import only styles that do not have urls to images! Include them directly in the page!
|
||||
@import "../bower_components/bootstrap-datepicker/css/datepicker.css"
|
||||
@import "../bower_components/angular-ui/build/angular-ui.min.css"
|
||||
@import "../bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css"
|
||||
@import "../bower_components/bootstrap-tour/build/css/bootstrap-tour.min.css"
|
||||
@import "../bower_components/angular-loading-bar/build/loading-bar.css"
|
||||
|
||||
// Custom includes
|
||||
@import "./female_sprites.styl";
|
||||
@import "./male_sprites.styl";
|
||||
@import "./shop_sprites.styl";
|
||||
@import "./tasks.styl";
|
||||
@import "./avatar.styl";
|
||||
@import "./customizer.styl";
|
||||
@import "./items.styl";
|
||||
@import "./inventory.styl";
|
||||
@import "./alerts.styl";
|
||||
@import "../../public/img/sprites/pet_sprites.css";
|
||||
@import "../../public/img/sprites/PetEggs.css";
|
||||
@import "./helpers.styl";
|
||||
@import "./responsive.styl";
|
||||
@import "./header.styl";
|
||||
@import "./filters.styl";
|
||||
@import "./scrollbars.styl";
|
||||
@import "./achievements.styl";
|
||||
@import "./game-pane.styl";
|
||||
@import "./backer.styl";
|
||||
@import "./tasks.styl"
|
||||
@import "./avatar.styl"
|
||||
@import "./customizer.styl"
|
||||
@import "./items.styl"
|
||||
@import "./inventory.styl"
|
||||
@import "./alerts.styl"
|
||||
@import "./helpers.styl"
|
||||
@import "./responsive.styl"
|
||||
@import "./header.styl"
|
||||
@import "./filters.styl"
|
||||
@import "./scrollbars.styl"
|
||||
@import "./game-pane.styl"
|
||||
@import "./npcs.styl"
|
||||
@import "./challenges.styl"
|
||||
|
||||
// fix exploding to very wide for some reason
|
||||
.datepicker
|
||||
|
|
@ -146,7 +144,33 @@ hr
|
|||
position: fixed;
|
||||
top: 0;
|
||||
right: 0px;
|
||||
z-index: 1001;
|
||||
z-index: 1061;
|
||||
|
||||
.modal-body
|
||||
margin: 10px
|
||||
|
||||
|
||||
// TODO this is supposed to go in avatar.styl, but stylus is breaking if I even touch that file
|
||||
.profile-modal-header
|
||||
width:560px
|
||||
margin: 0px auto
|
||||
|
||||
.avatar-level
|
||||
position absolute
|
||||
bottom 0px
|
||||
right 0px
|
||||
background-color #dfe9ea
|
||||
padding 1px 3px 1px 3px
|
||||
|
||||
a
|
||||
cursor: pointer
|
||||
|
||||
#modalMember p
|
||||
word-wrap: break-word
|
||||
|
||||
.btn
|
||||
margin-right: 5px
|
||||
|
||||
.modal-indented-list
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
.gem-wallet
|
||||
cursor: pointer
|
||||
padding-top: 10px;
|
||||
.tile
|
||||
background-color: darken($neutral, 10%)
|
||||
.add-gems-btn
|
||||
|
|
@ -11,6 +12,9 @@
|
|||
.tile
|
||||
opacity: 1
|
||||
|
||||
.modal-header .gem-wallet
|
||||
padding-top: 0px
|
||||
|
||||
|
||||
// pets (this will all change when pet system is overhauled)
|
||||
.pet-grid
|
||||
|
|
@ -20,13 +24,24 @@
|
|||
width: 100%
|
||||
td
|
||||
padding: 0.5em
|
||||
width: 25%
|
||||
//width: 25%
|
||||
&.active-pet
|
||||
background-color: $bad
|
||||
outline: 1px solid rgba(0,0,0,0.1)
|
||||
outline-offset: -1px
|
||||
&:hover, &:focus
|
||||
background-color: darken($better, 10%)
|
||||
> div
|
||||
margin:auto
|
||||
margin-bottom:.5em
|
||||
p
|
||||
text-align:center
|
||||
width:6.5em
|
||||
height:2.5em
|
||||
|
||||
menu.pets div
|
||||
display: inline-block
|
||||
padding-top: -30px
|
||||
|
||||
.current-pet
|
||||
left: 0px
|
||||
|
|
@ -38,16 +53,42 @@
|
|||
padding-bottom:20px
|
||||
|
||||
.Pet_Food_Base, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Veteran, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Base, .Pet_Food_Base, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Base, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Base, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Base, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Base, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Base, .Pet_Food_SugarCube, .Pet_Food_Strawberry, .Pet_Food_Rotten, .Pet_Food_Licorice, .Pet_Food_Golden, .Pet_Food_Cream, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Chocolate, .Pet_Food_Base, .Pet_Food_Zombie, .Pet_Food_White, .Pet_Food_Skeleton, .Pet_Food_Shade, .Pet_Food_Red, .Pet_Food_Golden, .Pet_Food_Desert, .Pet_Food_CottonCandyPink, .Pet_Food_CottonCandyBlue, .Pet_Food_Base
|
||||
background: url("/img/hatching_powder.png") no-repeat
|
||||
background: url("/bower_components/habitrpg-shared/img/hatching_powder.png") no-repeat
|
||||
width:34px
|
||||
height:34px
|
||||
|
||||
.Pet_Currency_Gem, .Pet_Currency_Gem2x, .Pet_Currency_Gem1x
|
||||
background: url("/bower_components/habitrpg-shared/img/sprites/Egg_Sprite_Sheet.png") no-repeat
|
||||
display:block
|
||||
|
||||
.Pet_Currency_Gem {background-position: 0px -510px; width: 51px; height: 45px} /* Not an egg or potion so has a different size */
|
||||
.Pet_Currency_Gem2x {background-position: -55px -513px; width: 34px; height: 30px}
|
||||
.Pet_Currency_Gem1x {background-position: -63px -542px; width: 19px; height: 17px}
|
||||
|
||||
.inventory-list p
|
||||
//display: none
|
||||
.inventory-list li
|
||||
clear:both
|
||||
.pets-menu > div
|
||||
float:left
|
||||
.hatchingPotions-menu > div
|
||||
float:left
|
||||
display:inline-block
|
||||
vertical-align:top
|
||||
padding:.3em
|
||||
width:6em
|
||||
margin-top:1em
|
||||
p
|
||||
text-align:center
|
||||
width:6em
|
||||
margin-top:-.5em
|
||||
.hatchingPotion-menu > div
|
||||
display:inline-block
|
||||
vertical-align:top
|
||||
padding:.3em
|
||||
width:6em
|
||||
margin-top:1em
|
||||
p
|
||||
text-align:center
|
||||
width:6em
|
||||
margin-top:-.5em
|
||||
|
||||
.pet-button
|
||||
border: none
|
||||
|
|
@ -62,3 +103,10 @@
|
|||
-moz-filter: brightness(0%)
|
||||
-o-filter: brightness(0%)
|
||||
-ms-filter: brightness(0%)*/
|
||||
|
||||
.selectableInventory
|
||||
background-color: lightgreen !important
|
||||
|
||||
.sell-inventory
|
||||
width: 162px
|
||||
height: 138px
|
||||
36
public/css/npcs.styl
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
.NPC-Alex, .NPC-Bailey, .NPC-Bailey-Head, .NPC-Daniel, .NPC-Justin, .NPC-Justin-Head, .NPC-Matt {background: url("/bower_components/habitrpg-shared/img/npcs/NPC-SpriteSheet.png") no-repeat}
|
||||
|
||||
.NPC-Alex {background-position: 0 0; width: 162px; height: 138px;}
|
||||
.NPC-Alex-container{margin-bottom: 20px;}
|
||||
.NPC-Daniel {background-position: -222px 0; width: 135px; height: 123px}
|
||||
.NPC-Justin {background-position: -357px 0; width: 84px; height: 119px}
|
||||
.NPC-Justin-Head {background-position: -396px 0; width: 36px; height: 96px}
|
||||
.NPC-Matt {background-position: -441px 0; width: 100%; padding-left: 208px; padding-bottom: 20px;}
|
||||
|
||||
// Bailey
|
||||
.NPC-Bailey
|
||||
background-position: -162px -42px
|
||||
width: 60px
|
||||
height: 72px
|
||||
float:left
|
||||
.NPC-Bailey-Head
|
||||
background-position: -168px -42px
|
||||
width: 54px
|
||||
height: 30px
|
||||
position: absolute
|
||||
top: 117px // 147 (header-height) - 30 (bailey height)
|
||||
right: 50px
|
||||
cursor: pointer
|
||||
|
||||
|
||||
.static-popover
|
||||
z-index 0
|
||||
display block
|
||||
position:relative
|
||||
|
||||
// Tour (Justin)
|
||||
.NPC-Justin.float-left
|
||||
float: left
|
||||
margin-right: 5px
|
||||
margin-bottom: 5px
|
||||
.popover-navigation {clear:both;}
|
||||
|
|
@ -14,4 +14,8 @@
|
|||
}
|
||||
body {
|
||||
padding-top:0px;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
6
public/css/static.styl
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Vendor Includes - include first so we can override
|
||||
// Import only styles that do not have urls to images! Include them directly in the page!
|
||||
@import "../bower_components/angular-loading-bar/build/loading-bar.css"
|
||||
|
||||
@import "./static-pages.css"
|
||||
@import "./footer.css"
|
||||
|
|
@ -71,7 +71,7 @@ for $stage in $stages
|
|||
box-shadow: none
|
||||
background-color: white
|
||||
height: 3em
|
||||
padding: 0 0 0 0.5em
|
||||
padding: 0 3.3em 0 0.5em
|
||||
width: 100%
|
||||
&:focus
|
||||
box-shadow: inset 0 0 3px darken($best, 20%),inset -1px 0 1px darken($best, 30%)
|
||||
|
|
@ -117,6 +117,11 @@ for $stage in $stages
|
|||
display: block
|
||||
padding: 0.75em 0 0.75em 3.5em
|
||||
line-height: 1.4
|
||||
word-wrap: break-word
|
||||
|
||||
// task due date (for to-dos)
|
||||
.task-date
|
||||
font-size: 70%
|
||||
|
||||
.habit-wide .task-text
|
||||
padding-left: 7em
|
||||
|
|
@ -326,7 +331,7 @@ form
|
|||
|
||||
.option-short
|
||||
.option-content
|
||||
width: 3em
|
||||
width: 4em
|
||||
display: inline-block
|
||||
.input-suffix
|
||||
vertical-align: 20%
|
||||
|
Before Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 387 B |
|
Before Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |