starts cleaning up the repo

This commit is contained in:
Matteo Pagliazzi 2015-10-30 14:49:27 +01:00
parent e8fa22bf93
commit e145439b0b
19 changed files with 1 additions and 1174 deletions

2
.gitignore vendored
View file

@ -16,7 +16,7 @@ newrelic_agent.log
.bower-cache
.vagrant
Vagrantfile
TODO
*.log
src/*/*.map
src/*/*/*.map

1
.nvmrc
View file

@ -1 +0,0 @@
0.10.40

View file

@ -1,64 +0,0 @@
<a name="">HabitRPG</a>
# (2014-01-28)
## Documentation
- **rebirth:** Bullet point about repurchase of limited ed gear after Rebirth
([d3f4a561](https://github.com/habitrpg/habitrpg/commits/d3f4a561fdf137e5d8f406bae03be4fef1caff22))
## Bug Fixes
- **#2003:** healer gear not showing
([949cd97b](https://github.com/habitrpg/habitrpg/commits/949cd97b91b42e9450eba559bbfea17e239ab100))
- **#2375:** merge in @SabreCat's stats.jade changes "More elegant show/hide setup for attribute bonuses"
([518f200a](https://github.com/habitrpg/habitrpg/commits/518f200a8fc7373b44ed7d7b5f016d921b0746bd))
- **beastmaster:** fixes #2557, adds opacity to previously-owned pets after they're mounted. You can earn them back again
([5caaff1c](https://github.com/habitrpg/habitrpg/commits/5caaff1cea1a68fe572e7ddf4aac50248b13df5d))
- **bosses:** don't reset progress.up when starting a new quest. We want to be able to carry over damage from the same day a boss battle begins, even if the dailies were completed before battle-start. Fixes #2168
([4efd0f5e](https://github.com/habitrpg/habitrpg/commits/4efd0f5ed8708f2491dd483f93e3d7a268a6337d))
- **classes:**
- misc fixes
([d2121a85](https://github.com/habitrpg/habitrpg/commits/d2121a858716cb5a532a53ee9c5a1adaa74a7f69))
- misc class fixes (not @snicker, ng-if on item store since we dynamically swap it sometimes)
([478be611](https://github.com/habitrpg/habitrpg/commits/478be6111337cd200374f7f31b959725c6a0b945))
- **find_uniq_user:** fix
([ecbe780e](https://github.com/habitrpg/habitrpg/commits/ecbe780e70549b1470504efe052f238c89a9db14))
- **mounts:** Move avatar upward when mounted regardless of pet
([bc1adeb1](https://github.com/habitrpg/habitrpg/commits/bc1adeb1277103a5ca1f756e175ed68bbe837a2f))
- **nodemon:** ignore CHANGELOG.md on watch
([d6c55952](https://github.com/habitrpg/habitrpg/commits/d6c55952da8b49f36e9d8e4570d80931d081343d))
- **party:** Round boss health up instead of to nearest integer
([626da568](https://github.com/habitrpg/habitrpg/commits/626da5681f5ea95700f8ddf40587c7184926971c),
[#2504](https://github.com/habitrpg/habitrpg/issues/2504))
- **paypal:** fixes #2492, remove environment check for now, only have production-mode option. revisit
([1dc68112](https://github.com/habitrpg/habitrpg/commits/1dc68112d131e4ebdec32ddff938eb6311d6565f))
- **profile:** fix bug where empty profile displayed on username click
([0579c432](https://github.com/habitrpg/habitrpg/commits/0579c432489c4a038e8c9f95ea3b285f5abc146f),
[#2465](https://github.com/habitrpg/habitrpg/issues/2465))
- **quests:**
- bug fix to multi-drop
([f478d10c](https://github.com/habitrpg/habitrpg/commits/f478d10c20f816cd104b3f0da814c189957f45f5))
- list multiple rewards in dialog
([e48c7277](https://github.com/habitrpg/habitrpg/commits/e48c7277f8256cf827790aece51e897fe0439374))
- **settings:** reintroduce space between captions and help bubbles stripped during localization
([5ddf09fe](https://github.com/habitrpg/habitrpg/commits/5ddf09fe13c7f8d844c8c47be0fb8f8b2fd1df33))
- **spells:**
- more $rootScope spell-casting bug fixes
([47bd6dcb](https://github.com/habitrpg/habitrpg/commits/47bd6dcb79778d90d6f3ddeb003c3d8e45433333))
- add some spells tests, don't send up body to spell paths
([e0646bb9](https://github.com/habitrpg/habitrpg/commits/e0646bb98d44b6874b5259107c9be5fa34c58933))
- some $rootScope.applying action fixes so cast-ending is immediate instead of waiting on response. Also, slim down party population to the essentials to avoid RequestEntityTooLarge
([c6f7ab8a](https://github.com/habitrpg/habitrpg/commits/c6f7ab8a5c6f4e382208a928b90ba5f4eba9cd37))
- <ESC> to cancel spell-casting
([a1df41ad](https://github.com/habitrpg/habitrpg/commits/a1df41ad8165cd9eb6d2d5d59c7fe404edde716c))
- **stable:** show hatchable combo when petOwned>0 (fyi @deilann)
([51bff238](https://github.com/habitrpg/habitrpg/commits/51bff23885ca0080e7e71ff752daa0950ae923ae))
- **stats:** Better layout for attribute point allocation
([d782fc6b](https://github.com/habitrpg/habitrpg/commits/d782fc6b6a3cd7e90d327c93a5764626b2990c74))
- **tests:**
- include select2 in test manifest
([38b4cea7](https://github.com/habitrpg/habitrpg/commits/38b4cea73299f51c4db7f6b2eb12533d219745f8))
- don't use cluster in tests, else we get "connection refused"
([7a479098](https://github.com/habitrpg/habitrpg/commits/7a479098dc6535654e322c737d80813790967941)

View file

@ -1,34 +0,0 @@
<a name="">My app - Changelog</a>
# (2015-05-08)
## Bug Fixes
- **Spring:** WHO IS LESLIE
([6685e935](watch/commits/6685e93554a1274dedb55dae054e787fb80eb440))
- **invite-friends:** text should be valid for both parties and guilds
([54e82a14](watch/commits/54e82a14a252c3b9923449a7ef7cee6033a5d160))
- **mystery:** It's 2015 now, Sabe
([00252f20](watch/commits/00252f200481f06de9bccd1e55275d5366b03919))
- **scoring:** move gainMP into score
([2fc0cb8f](watch/commits/2fc0cb8fa1b3b5975c16653cb110be2f03b5427e))
- **slimes:** Tweaks and missing sprite
([7d1a58ca](watch/commits/7d1a58ca002af9dac19aba65d6465bd23b28d649))
## Features
- **Spring:** Flung
([d50d4ad8](watch/commits/d50d4ad8bb0f89e39ceb6562e0f8f392f94b5444))
- **emails:** add support for weekly recap emails
([37f7db3c](watch/commits/37f7db3c4e3859d03fd55a44e63819e273a06442))
- **i18n:** upload japanese, serbian and chinese (taiwan)
([ee7ba19e](watch/commits/ee7ba19ed17e72b33cbef8a324266617d384f852))
- **mystery:** April Subscribee Items
([7a7fc968](watch/commits/7a7fc96818ffd7f92738e8c6cc8a59e48d60597d))
- **pets:** Slime Quest
([f13c6cba](watch/commits/f13c6cba0026c645b19a0b1355ba2c5b27f80878))
- **quests:** Boss damage from Habits
([43dcded0](watch/commits/43dcded051b602d8a4efc30eef45365abfd238b4))
- **scoring:** MP gain from Habits and Dailies
([7b22244f](watch/commits/7b22244f0123cf649c9f2aada0811f35a565688d))

View file

@ -1,8 +0,0 @@
# What's This?
I'm consolidating @litenull's rewrite branch with the old code, and removing files from both sources once they've been
successfully merged into the new platform. While @litenull's "from scratch" approach was really clean, it will take
us longer to implemente all the original features. This approach will (1) let us leverage code we already have, (2) merge
in litenull's hard work from the last few weeks.
Once this archive/ directory is completely empty, we should be fully merged and ready to deploy the rewrite!

View file

@ -1,46 +0,0 @@
_ = require 'lodash'
moment = require 'moment'
###
Loads JavaScript files from public/vendor/*
Use require() to min / concatinate for faster page load
###
loadJavaScripts = (model) ->
# Turns out you can't have expressions in browserify require() statements
#vendor = '../../public/vendor'
#require "#{vendor}/jquery-ui-1.10.2/jquery-1.9.1"
###
Internal Scripts
###
require "../vendor/jquery.cookie.min.js"
require "../vendor/bootstrap/js/bootstrap.min.js"
require "../vendor/datepicker/js/bootstrap-datepicker"
require "../vendor/bootstrap-tour/bootstrap-tour"
unless (model.get('_mobileDevice') is true)
require "../vendor/sticky"
# note: external script loading is handled in app.on('render') near the bottom of this file (see https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/x8FwdTLEuXo)
# jquery sticky header on scroll, no need for position fixed
initStickyHeader = (model) ->
$('.header-wrap').sticky({topSpacing:0})
module.exports.app = (appExports, model, app) ->
app.on 'render', (ctx) ->
#restoreRefs(model)
unless model.get('_mobileDevice')
setupTooltips(model)
initStickyHeader(model)
setupSortable(model)
$('.datepicker').datepicker({autoclose:true, todayBtn:true})
.on 'changeDate', (ev) ->
#for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
model.at(ev.target).set 'date', moment(ev.date).format('MM/DD/YYYY')

View file

@ -1,239 +0,0 @@
_ = require 'lodash'
{helpers} = require 'habitrpg-shared'
async = require 'async'
module.exports.app = (app) ->
###
Sync any updates to challenges since last refresh. Do it after cron, so we don't punish them for new tasks
This is challenge->user sync. user->challenge happens when user interacts with their tasks
###
app.on 'ready', (model) ->
window.setTimeout ->
_.each model.get('groups'), (g) ->
if (@uid in g.members) and g.challenges
_.each(g.challenges, ->app.challenges.syncChalToUser g)
true
, 500
###
Sync user to challenge (when they score, add to statistics)
###
app.model.on "change", "_page.user.priv.tasks.*.value", (id, value, previous, passed) ->
### Sync to challenge, but do it later ###
async.nextTick =>
model = app.model
ctx = {model: model}
task = model.at "_page.user.priv.tasks.#{id}"
tobj = task.get()
pub = model.get "_page.user.pub"
if (chalTask = helpers.taskInChallenge.call ctx, tobj)? and chalTask.get()
chalTask.increment "value", value - previous
chal = model.at "groups.#{tobj.group.id}.challenges.#{tobj.challenge}"
chalUser = -> helpers.indexedAt.call(ctx, chal.path(), 'members', {id:pub.id})
cu = chalUser()
unless cu?.get()
chal.push "members", {id: pub.id, name: model.get(pub.profile.name)}
cu = model.at chalUser()
else
cu.set 'name', pub.profile.name # update their name incase it changed
cu.set "#{tobj.type}s.#{tobj.id}",
value: tobj.value
history: tobj.history
###
Render graphs for user scores when the "Challenges" tab is clicked
###
###
TODO
1) on main tab click or party
* sort & render graphs for party
2) guild -> all guilds
3) public -> all public
###
###
$('#profile-challenges-tab-link').on 'shown', ->
async.each _.toArray(model.get('groups')), (g) ->
async.each _.toArray(g.challenges), (chal) ->
async.each _.toArray(chal.tasks), (task) ->
async.each _.toArray(chal.members), (member) ->
if (history = member?["#{task.type}s"]?[task.id]?.history) and !!history
data = google.visualization.arrayToDataTable _.map(history, (h)-> [h.date,h.value])
options =
backgroundColor: { fill:'transparent' }
width: 150
height: 50
chartArea: width: '80%', height: '80%'
axisTitlePosition: 'none'
legend: position: 'bottom'
hAxis: gridlines: color: 'transparent' # since you can't seem to *remove* gridlines...
vAxis: gridlines: color: 'transparent'
chart = new google.visualization.LineChart $(".challenge-#{chal.id}-member-#{member.id}-history-#{task.id}")[0]
chart.draw(data, options)
###
app.fn
challenges:
###
Create
###
create: (e,el) ->
[type, gid] = [$(el).attr('data-type'), $(el).attr('data-gid')]
cid = @model.id()
@model.set '_page.new.challenge',
id: cid
name: ''
habits: []
dailys: []
todos: []
rewards: []
user:
uid: @uid
name: @pub.get('profile.name')
group: {type, id:gid}
timestamp: +new Date
###
Save
###
save: ->
newChal = @model.get('_page.new.challenge')
[gid, cid] = [newChal.group.id, newChal.id]
@model.push "_page.lists.challenges.#{gid}", newChal, ->
app.browser.growlNotification('Challenge Created','success')
app.challenges.discard()
app.browser.resetDom() # something is going absolutely haywire here, all model data at end of reflist after chal created
###
Toggle Edit
###
toggleEdit: (e, el) ->
path = "_page.editing.challenges.#{$(el).attr('data-id')}"
@model.set path, !@model.get(path)
###
Discard
###
discard: ->
@model.del '_page.new.challenge'
###
Delete
###
delete: (e) ->
return unless confirm("Delete challenge, are you sure?") is true
e.at().remove()
###
Add challenge name as a tag for user
###
syncChalToUser: (chal) ->
return unless chal
### Sync tags ###
tags = @priv.get('tags') or []
idx = _.findIndex tags, {id: chal.id}
if ~idx and (tags[idx].name isnt chal.name)
### update the name - it's been changed since ###
@priv.set "tags.#{idx}.name", chal.name
else
@priv.push 'tags', {id: chal.id, name: chal.name, challenge: true}
tags = {}; tags[chal.id] = true
_.each chal.habits.concat(chal.dailys.concat(chal.todos.concat(chal.rewards))), (task) =>
_.defaults task, { tags, challenge: chal.id, group: {id: chal.group.id, type: chal.group.type} }
path = "tasks.#{task.id}"
if @priv.get path
@priv.set path, _.defaults(@priv.get(path), task)
else
@model.push "_page.lists.tasks.#{@uid}.#{task.type}s", task
true
###
Subscribe
###
subscribe: (e) ->
chal = e.get()
### Add all challenge's tasks to user's tasks ###
currChallenges = @pub.get('challenges')
@pub.unshift('challenges', chal.id) unless currChallenges and ~currChallenges.indexOf(chal.id)
e.at().push "members",
id: @uid
name: @pub.get('profile.name')
app.challenges.syncChalToUser(chal)
###
--------------------------
Unsubscribe functions
--------------------------
###
unsubscribe: (chal, keep=true) ->
### Remove challenge from user ###
i = @pub.get('challenges')?.indexOf(chal.id)
if i? and ~i
@pub.remove("challenges", i, 1)
### Remove user from challenge ###
if ~(i = _.findIndex chal.members, {id: @uid})
@model.remove "groups.#{chal.group.id}.challenges.#{chal.id}.members", i, 1
### Remove tasks from user ###
async.each chal.habits.concat(chal.dailys.concat(chal.todos.concat(chal.rewards))), (task) =>
if keep is true
@priv.del "tasks.#{task.id}.challenge"
else
path = "_page.lists.tasks.#{@uid}.#{task.type}s"
if ~(i = _.findIndex(@model.get(path), {id:task.id}))
@model.remove(path, i, 1)
true
taskUnsubscribe: (e, el) ->
###
since the challenge was deleted, we don't have its data to unsubscribe from - but we have the vestiges on the task
FIXME this is a really dumb way of doing this
###
tasks = @priv.get('tasks')
tobj = tasks[$(el).attr("data-tid")]
deletedChal =
id: tobj.challenge
members: [@uid]
habits: _.where tasks, {type: 'habit', challenge: tobj.challenge}
dailys: _.where tasks, {type: 'daily', challenge: tobj.challenge}
todos: _.where tasks, {type: 'todo', challenge: tobj.challenge}
rewards: _.where tasks, {type: 'reward', challenge: tobj.challenge}
switch $(el).attr('data-action')
when 'keep'
@priv.del "tasks.#{tobj.id}.challenge"
@priv.del "tasks.#{tobj.id}.group"
when 'keep-all'
app.challenges.unsubscribe.call @, deletedChal, true
when 'remove'
path = "_page.lists.tasks.#{@uid}.#{tobj.type}s"
if ~(i = _.findIndex @model.get(path), {id: tobj.id})
@model.remove path, i
when 'remove-all'
app.challenges.unsubscribe.call @, deletedChal, false
challengeUnsubscribe: (e, el) ->
$(el).popover('destroy').popover({
html: true
placement: 'top'
trigger: 'manual'
title: 'Unsubscribe From Challenge And:'
content: """
<a class=challenge-unsubscribe-and-remove>Remove Tasks</a><br/>
<a class=challenge-unsubscribe-and-keep>Keep Tasks</a><br/>
<a class=challenge-unsubscribe-cancel>Cancel</a><br/>
"""
}).popover('show')
$('.challenge-unsubscribe-and-remove').click => app.challenges.unsubscribe.call @, e.get(), false
$('.challenge-unsubscribe-and-keep').click => app.challenges.unsubscribe.call @, e.get(), true
$('[class^=challenge-unsubscribe]').click => $(el).popover('destroy')

View file

@ -1,22 +0,0 @@
_ = require 'lodash'
module.exports.app = (appExports, model) ->
user = model.at('_user')
appExports.filtersDeleteTag = (e, el) ->
tags = user.get('tags')
tag = e.at "_user.tags." + $(el).attr('data-index')
tagId = tag.get('id')
###something got corrupted, let's clear the corrupt tags###
unless tagId
user.set 'tags', _.filter( tags, ((t)-> t?.id) )
user.set 'filters', {}
return
model.del "_user.filters.#{tagId}"
tag.remove()
### remove tag from all tasks###
_.each user.get("tasks"), (task) -> user.del "tasks.#{task.id}.tags.#{tagId}"; true

View file

@ -1,139 +0,0 @@
_ = require('lodash')
helpers = require('habitrpg-shared/script/helpers')
module.exports.app = (appExports, model, app) ->
browser = require './browser'
_currentTime = model.at '_currentTime'
_currentTime.setNull +new Date
# Every 60 seconds, reset the current time so that the chat can update relative times
setInterval (->_currentTime.set +new Date), 60000
appExports.groupCreate = (e,el) ->
type = $(el).attr('data-type')
newGroup =
name: model.get("_new.group.name")
description: model.get("_new.group.description")
leader: user.get('id')
members: [user.get('id')]
type: type
# parties - free
if type is 'party'
return model.add 'groups', newGroup, ->location.reload()
# guilds - 4G
unless user.get('balance') >= 1
return $('#more-gems-modal').modal 'show'
if confirm "Create Guild for 4 Gems?"
newGroup.privacy = (model.get("_new.group.privacy") || 'public') if type is 'guild'
newGroup.balance = 1 # they spent $ to open the guild, it goes into their guild bank
model.add 'groups', newGroup, ->
user.incr 'balance', -1, ->location.reload()
appExports.toggleGroupEdit = (e, el) ->
path = "_editing.groups.#{$(el).attr('data-gid')}"
model.set path, !model.get(path)
appExports.toggleLeaderMessageEdit = (e, el) ->
path = "_editing.leaderMessage.#{$(el).attr('data-gid')}"
model.set path, !model.get(path)
appExports.groupAddWebsite = (e, el) ->
test = e.get()
e.at().unshift 'websites', model.get('_newGroupWebsite')
model.del '_newGroupWebsite'
appExports.groupInvite = (e,el) ->
uid = model.get('_groupInvitee').replace(/[\s"]/g, '')
model.set '_groupInvitee', ''
return if _.isEmpty(uid)
model.query('users').publicInfo([uid]).fetch (err, profiles) ->
throw err if err
profile = profiles.at(0).get()
return model.set("_groupError", "User with id #{uid} not found.") unless profile
model.query('groups').withMember(uid).fetch (err, g) ->
throw err if err
group = e.get(); groups = g.get()
{type, name} = group; gid = group.id
groupError = (msg) -> model.set("_groupError", msg)
invite = ->
$.bootstrapGrowl "Invitation Sent."
switch type
when 'guild' then model.push "users.#{uid}.invitations.guilds", {id:gid, name}, ->location.reload()
when 'party' then model.set "users.#{uid}.invitations.party", {id:gid, name}, ->location.reload()
switch type
when 'guild'
if profile.invitations?.guilds and _.find(profile.invitations.guilds, {id:gid})
return groupError("User already invited to that group")
else if uid in group.members
return groupError("User already in that group")
else invite()
when 'party'
if profile.invitations?.party
return groupError("User already pending invitation.")
else if _.find(groups, {type:'party'})
return groupError("User already in a party.")
else invite()
appExports.acceptInvitation = (e,el) ->
gid = e.get('id')
if $(el).attr('data-type') is 'party'
user.set 'invitations.party', null, ->joinGroup(gid)
else
e.at().remove ->joinGroup(gid)
appExports.rejectInvitation = (e, el) ->
clear = -> browser.resetDom(model)
if e.at().path().indexOf('party') != -1
model.del e.at().path(), clear
else e.at().remove clear
appExports.groupLeave = (e,el) ->
if confirm("Leave this group, are you sure?") is true
uid = user.get('id')
group = model.at "groups.#{$(el).attr('data-id')}"
index = group.get('members').indexOf(uid)
if index != -1
group.remove 'members', index, 1, ->
updated = group.get()
# last member out, delete the party
if _.isEmpty(updated.members) and (updated.type is 'party')
group.del ->location.reload()
# assign new leader, so the party is editable #TODO allow old leader to assign new leader, this is just random
else if (updated.leader is uid)
group.set "leader", updated.members[0], ->location.reload()
else location.reload()
###
Chat Functionality
###
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
model.on 'unshift', '_habitrpg.chat', -> $('.chat-message').tooltip()
appExports.chatKeyup = (e, el, next) ->
return next() unless e.keyCode is 13
appExports.sendChat(e, el)
appExports.deleteChatMessage = (e) ->
if confirm("Delete chat message?") is true
e.at().remove() #requires the {#with}
app.on 'render', (ctx) ->
$('#party-tab-link').on 'shown', (e) ->
messages = model.get('_party.chat')
return false unless messages?.length > 0
model.set '_user.party.lastMessageSeen', messages[0].id
appExports.gotoPartyChat = ->
model.set '_gamePane', true, ->
$('#party-tab-link').tab('show')
appExports.assignGroupLeader = (e, el) ->
newLeader = model.get('_new.groupLeader')
if newLeader and (confirm("Assign new leader, you sure?") is true)
e.at().set('leader', newLeader, ->browser.resetDom(model)) if newLeader

View file

@ -1,7 +0,0 @@
i18n = require 'derby-i18n'
i18n.plurals.add 'he', (n) -> n
i18n.plurals.add 'bg', (n) -> n
i18n.plurals.add 'nl', (n) -> n
module.exports = i18n

View file

@ -1,20 +0,0 @@
# Translations
i18n = require './i18n'
i18n.localize app,
availableLocales: ['en', 'he', 'bg', 'nl']
defaultLocale: 'en'
urlScheme: false
checkHeader: true
# ========== CONTROLLER FUNCTIONS ==========
ready (model) ->
misc.fixCorruptUser(model) # https://github.com/lefnire/habitrpg/issues/634
# used for things like remove website, chat, etc
exports.removeAt = (e, el) ->
if (confirmMessage = $(el).attr 'data-confirm')?
return unless confirm(confirmMessage) is true
e.at().remove()
browser.resetDom(model) if $(el).attr('data-refresh')

View file

@ -1,39 +0,0 @@
items = require 'habitrpg-shared/script/items'
_ = require 'lodash'
updateStore = (model) ->
nextItems = items.updateStore(model.get('_user'))
_.each nextItems, (v,k) -> model.set("_items.next.#{k}",v); true
###
server exports
###
module.exports.server = (model) ->
model.set '_items', items.items
updateStore(model)
###
app exports
###
module.exports.app = (appExports, model) ->
misc = require './misc'
model.on "set", "_user.items.*", -> updateStore(model)
appExports.buyItem = (e, el) ->
misc.batchTxn model, (uObj, paths) ->
ret = items.buyItem uObj, $(el).attr('data-type'), {paths}
alert("Not enough GP") if ret is false
appExports.activateRewardsTab = ->
model.set '_activeTabRewards', true
model.set '_activeTabPets', false
appExports.activatePetsTab = ->
model.set '_activeTabPets', true
model.set '_activeTabRewards', false

View file

@ -1,152 +0,0 @@
_ = require 'lodash'
algos = require 'habitrpg-shared/script/algos'
items = require('habitrpg-shared/script/items').items
helpers = require('habitrpg-shared/script/helpers')
#TODO put this in habitrpg-shared
###
We can't always use refLists, but we often still need to get a positional path by id: eg, users.1234.tasks.5678.value
For arrays (which use indexes, not id-paths), here's a helper function so we can run indexedPath('users',:user.id,'tasks',:task.id,'value)
###
indexedPath = ->
_.reduce arguments, (m,v) =>
return v if !m #first iteration
return "#{m}.#{v}" if _.isString v #string paths
return "#{m}." + _.findIndex(@model.get(m),v)
, ''
taskInChallenge = (task) ->
return undefined unless task?.challenge
@model.at indexedPath.call(@, "groups.#{task.group.id}.challenges", {id:task.challenge}, "#{task.type}s", {id:task.id})
###
algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the
object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first
clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination),
perform the updates while tracking paths, then all the values at those paths
###
module.exports.score = (model, taskId, direction, allowUndo=false) ->
drop = undefined
delta = batchTxn model, (uObj, paths) ->
tObj = uObj.tasks[taskId]
# Stuff for undo
if allowUndo
tObjBefore = _.cloneDeep tObj
tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo']
previousUndo = model.get('_undo')
clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId
timeoutId = setTimeout (-> model.del('_undo')), 20000
model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId}
delta = algos.score(uObj, tObj, direction, {paths})
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
drop = uObj._tmp?.drop
# Update challenge statistics
# FIXME put this in it's own batchTxn, make batchTxn model.at() ref aware (not just _user)
# FIXME use reflists for users & challenges
if (chalTask = taskInChallenge.call({model}, tObj)) and chalTask?.get()
model._dontPersist = false
chalTask.incr "value", delta
chal = model.at indexedPath.call({model}, "groups.#{tObj.group.id}.challenges", {id:tObj.challenge})
chalUser = -> indexedPath.call({model}, chal.path(), 'users', {id:uObj.id})
cu = model.at chalUser()
unless cu?.get()
chal.push "users", {id: uObj.id, name: helpers.username(uObj.auth, uObj.profile?.name)}
cu = model.at chalUser()
else
cu.set 'name', helpers.username(uObj.auth, uObj.profile?.name) # update their name incase it changed
cu.set "#{tObj.type}s.#{tObj.id}",
value: tObj.value
history: tObj.history
model._dontPersist = true
, done:->
if drop and $?
model.set '_drop', drop
$('#item-dropped-modal').modal 'show'
delta
###
Cleanup task-corruption (null tasks, rogue/invisible tasks, etc)
Obviously none of this should be happening, but we'll stop-gap until we can find & fix
Gotta love refLists! see https://github.com/lefnire/habitrpg/issues/803 & https://github.com/lefnire/habitrpg/issues/6343
###
module.exports.fixCorruptUser = (model) ->
user = model.at('_user')
tasks = user.get('tasks')
## Remove corrupted tasks
_.each tasks, (task, key) ->
unless task?.id? and task?.type?
user.del("tasks.#{key}")
delete tasks[key]
true
resetDom = false
batchTxn model, (uObj, paths, batch) ->
## fix https://github.com/lefnire/habitrpg/issues/1086
uniqPets = _.uniq(uObj.items.pets)
batch.set('items.pets', uniqPets) if !_.isEqual(uniqPets, uObj.items.pets)
if uObj.invitations?.guilds
uniqInvites = _.uniq(uObj.invitations.guilds)
batch.set('invitations.guilds', uniqInvites) if !_.isEqual(uniqInvites, uObj.invitations.guilds)
## Task List Cleanup
['habit','daily','todo','reward'].forEach (type) ->
# 1. remove duplicates
# 2. restore missing zombie tasks back into list
idList = uObj["#{type}Ids"]
taskIds = _.pluck( _.where(tasks, {type}), 'id')
union = _.union idList, taskIds
# 2. remove empty (grey) tasks
preened = _.filter union, (id) -> id and _.contains(taskIds, id)
# There were indeed issues found, set the new list
if !_.isEqual(idList, preened)
batch.set("#{type}Ids", preened)
console.error uObj.id + "'s #{type}s were corrupt."
true
resetDom = !_.isEmpty(paths)
require('./browser').resetDom(model) if resetDom
module.exports.viewHelpers = (view) ->
#misc
view.fn "percent", (x, y) ->
x=1 if x==0
Math.round(x/y*100)
view.fn 'indexOf', (str1, str2) ->
return false unless str1 && str2
str1.indexOf(str2) != -1
view.fn "round", Math.round
view.fn "floor", Math.floor
view.fn "ceil", Math.ceil
view.fn "truarr", (num) -> num-1
view.fn 'count', (arr) -> arr?.length or 0
view.fn 'int',
get: (num) -> num
set: (num) -> [parseInt(num)]
view.fn 'indexedPath', indexedPath
#iCal
view.fn "encodeiCalLink", helpers.encodeiCalLink
#User
view.fn "gems", (balance) -> balance * 4
#Challenges
view.fn 'taskInChallenge', (task) ->
taskInChallenge.call(@,task)?.get()
view.fn 'taskAttrFromChallenge', (task, attr) ->
taskInChallenge.call(@,task)?.get(attr)
view.fn 'brokenChallengeLink', (task) ->
task?.challenge and !(taskInChallenge.call(@,task)?.get())
view.fn 'challengeMemberScore', (member, tType, tid) ->
Math.round(member["#{tType}s"]?[tid]?.value)

View file

@ -1,73 +0,0 @@
_ = require 'lodash'
{ randomVal } = require 'habitrpg-shared/script/helpers'
{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items
###
app exports
###
module.exports.app = (appExports, model) ->
user = model.at '_user'
appExports.chooseEgg = (e, el) ->
model.ref '_hatchEgg', e.at()
appExports.hatchEgg = (e, el) ->
hatchingPotionName = $(el).children('select').val()
myHatchingPotion = user.get 'items.hatchingPotions'
egg = model.get '_hatchEgg'
eggs = user.get 'items.eggs'
myPets = user.get 'items.pets'
hatchingPotionIdx = myHatchingPotion.indexOf hatchingPotionName
eggIdx = eggs.indexOf egg
return alert "You don't own that hatching potion yet, complete more tasks!" if hatchingPotionIdx is -1
return alert "You don't own that egg yet, complete more tasks!" if eggIdx is -1
return alert "You already have that pet, hatch a different combo." if myPets and myPets.indexOf("#{egg.name}-#{hatchingPotionName}") != -1
user.push 'items.pets', egg.name + '-' + hatchingPotionName, ->
eggs.splice eggIdx, 1
myHatchingPotion.splice hatchingPotionIdx, 1
user.set 'items.eggs', eggs
user.set 'items.hatchingPotions', myHatchingPotion
alert 'Your egg hatched! Visit your stable to equip your pet.'
#FIXME Bug: this removes from the array properly in the browser, but on refresh is has removed all items from the arrays
# user.remove 'items.hatchingPotions', hatchingPotionIdx, 1
# user.remove 'items.eggs', eggIdx, 1
appExports.choosePet = (e, el, next) ->
petStr = $(el).attr('data-pet')
return next() if user.get('items.pets').indexOf(petStr) == -1
# If user's pet is already active, deselect it
return user.set 'items.currentPet', {} if user.get('items.currentPet.str') is petStr
[name, modifier] = petStr.split('-')
pet = _.find pets, {name: name}
pet.modifier = modifier
pet.str = petStr
user.set 'items.currentPet', pet
appExports.buyHatchingPotion = (e, el) ->
name = $(el).attr 'data-hatchingPotion'
newHatchingPotion = _.find hatchingPotions, {name: name}
gems = user.get('balance') * 4
if gems >= newHatchingPotion.value
if confirm "Buy this hatching potion with #{newHatchingPotion.value} of your #{gems} Gems?"
user.push 'items.hatchingPotions', newHatchingPotion.name
user.set 'balance', (gems - newHatchingPotion.value) / 4
else
$('#more-gems-modal').modal 'show'
appExports.buyEgg = (e, el) ->
name = $(el).attr 'data-egg'
newEgg = _.find pets, {name: name}
gems = user.get('balance') * 4
if gems >= newEgg.value
if confirm "Buy this egg with #{newEgg.value} of your #{gems} Gems?"
user.push 'items.eggs', newEgg
user.set 'balance', (gems - newEgg.value) / 4
else
$('#more-gems-modal').modal 'show'

View file

@ -1,29 +0,0 @@
algos = require 'habitrpg-shared/script/algos'
helpers = require 'habitrpg-shared/script/helpers'
_ = require 'lodash'
moment = require 'moment'
misc = require './misc'
appExports.clearCompleted = (e, el) ->
completedIds = _.pluck( _.where(model.get('_todoList'), {completed:true}), 'id')
todoIds = user.get('todoIds')
_.each completedIds, (id) -> user.del "tasks.#{id}"; true
user.set 'todoIds', _.difference(todoIds, completedIds)
###
Undo
###
appExports.undo = () ->
undo = model.get '_undo'
clearTimeout(undo.timeoutId) if undo?.timeoutId
model.del '_undo'
_.each undo.stats, (val, key) -> user.set "stats.#{key}", val; true
taskPath = "tasks.#{undo.task.id}"
_.each undo.task, (val, key) ->
return true if key in ['id', 'type'] # strange bugs in this world: https://workflowy.com/shared/a53582ea-43d6-bcce-c719-e134f9bf71fd/
if key is 'completed'
user.pass({cron:true}).set("#{taskPath}.completed",val)
else
user.set "#{taskPath}.#{key}", val
true

View file

@ -1,95 +0,0 @@
_ = require 'lodash'
{ randomVal } = require 'habitrpg-shared/script/helpers'
{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items
###
Listeners to enabled flags, set notifications to the user when they've unlocked features
###
module.exports.app = (appExports, model) ->
user = model.at('_user')
alreadyShown = (before, after) -> !(!before and after is true)
showPopover = (selector, title, html, placement='bottom') ->
$(selector).popover('destroy')
html += " <a href='#' onClick=\"$('#{selector}').popover('hide');return false;\">[Close]</a>"
$(selector).popover({
title: title
placement: placement
trigger: 'manual'
html: true
content: html
}).popover 'show'
user.on 'set', 'flags.customizationsNotification', (after, before) ->
return if alreadyShown(before,after)
$('.main-herobox').popover('destroy') #remove previous popovers
html = "Click your avatar to customize your appearance."
showPopover '.main-herobox', 'Customize Your Avatar', html, 'bottom'
user.on 'set', 'flags.itemsEnabled', (after, before) ->
return if alreadyShown(before,after)
html = """
<img src='/vendor/BrowserQuest/client/img/1/chest.png' />
Congratulations, you have unlocked the Item Store! You can now buy weapons, armor, potions, etc. Read each item's comment for more information.
"""
showPopover 'div.rewards', 'Item Store Unlocked', html, 'left'
user.on 'set', 'flags.petsEnabled', (after, before) ->
return if alreadyShown(before,after)
html = """
<img src='/img/sprites/wolf_border.png' style='width:30px;height:30px;float:left;padding-right:5px' />
You have unlocked Pets! You can now buy pets with Gems (note, you replenish Gems with real-life money - so chose your pets wisely!)
"""
showPopover '#rewardsTabs', 'Pets Unlocked', html, 'left'
user.on 'set', 'flags.partyEnabled', (after, before) ->
return if user.get('party.current') or alreadyShown(before,after)
html = """
Be social, join a party and play Habit with your friends! You'll be better at your habits with accountability partners. Click User -> Options -> Party, and follow the instructions. LFG anyone?
"""
showPopover '.user-menu', 'Party System', html, 'bottom'
user.on 'set', 'flags.dropsEnabled', (after, before) ->
return if alreadyShown(before,after)
egg = randomVal pets
dontPersist = model._dontPersist
model._dontPersist = false
user.push 'items.eggs', egg
model._dontPersist = dontPersist
$('#drops-enabled-modal').modal 'show'
user.on 'push', 'items.pets', (after, before) ->
return if user.get('achievements.beastMaster')
if before >= 90 # evidently before is the count?
dontPersist = model._dontPersist; model._dontPersist = false
user.set 'achievements.beastMaster', true, (-> model._dontPersist = dontPersist)
$('#beastmaster-achievement-modal').modal('show')
user.on 'set', 'items.*', (after, before) ->
return if user.get('achievements.ultimateGear')
items = user.get('items')
if parseInt(items.weapon) >= 6 and parseInt(items.armor) >= 5 and parseInt(items.head) >= 5 and parseInt(items.shield) >= 5
dontPersist = model._dontPersist; model._dontPersist = false
user.set 'achievements.ultimateGear', true, (->model._dontPersist = dontPersist)
$('#max-gear-achievement-modal').modal('show')
user.on 'set', 'tasks.*.streak', (id, after, before) ->
if after > 0
# 21-day streak, as per the old philosophy of doign a thing 21-days in a row makes a habit
if (after % 21) is 0
dontPersist = model._dontPersist; model._dontPersist = false
user.incr 'achievements.streak', 1, (-> model._dontPersist = dontPersist)
$('#streak-achievement-modal').modal('show')
# they're undoing a task at the 21 mark, take back their badge
else if (before - after is 1) and (before % 21 is 0)
dontPersist = model._dontPersist; model._dontPersist = false
user.incr 'achievements.streak', -1, (-> model._dontPersist = dontPersist)

View file

@ -1,25 +0,0 @@
/**
* New Relic agent configuration.
*
* See lib/config.defaults.js in the agent distribution for a more complete
* description of configuration variables and their potential values.
*/
var nconf = require('nconf')
exports.config = {
/**
* Array of application names.
*/
app_name : ['HabitRPG'],
/**
* Your New Relic license key.
*/
license_key : nconf.get('NEW_RELIC_LICENSE_KEY'),
logging : {
/**
* Level at which to log. 'trace' is most useful to New Relic when diagnosing
* issues with the agent, 'info' and higher will impose the least overhead on
* production applications.
*/
level : 'warning'
}
};

View file

@ -1,109 +0,0 @@
#!/usr/bin/env node
/**
* Git COMMIT-MSG hook for validating commit message
* From: https://github.com/angular/angular.js
* See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit
*
* Installation:
* >> cd <angular-repo>
* >> ln -s ../../validate-commit-msg.js .git/hooks/commit-msg
*/
var fs = require('fs');
var util = require('util');
var MAX_LENGTH = 999;
var PATTERN = /^(?:fixup!\s*)?(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
var IGNORED = /^WIP\:/;
var TYPES = {
feat: true,
fix: true,
docs: true,
style: true,
refactor: true,
perf: true,
test: true,
chore: true,
revert: true,
'interface': true,
i18n: true
};
var error = function() {
// gitx does not display it
// http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails
// https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812
console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments));
};
var validateMessage = function(message) {
var isValid = true;
if (IGNORED.test(message)) {
console.log('Commit message validation ignored.');
return true;
}
if (message.length > MAX_LENGTH) {
error('is longer than %d characters !', MAX_LENGTH);
isValid = false;
}
var match = PATTERN.exec(message);
if (!match) {
error('does not match "<type>(<scope>): <subject>" ! was: ' + message);
return false;
}
var type = match[1];
var scope = match[3];
var subject = match[4];
if (!TYPES.hasOwnProperty(type)) {
error('"%s" is not allowed type !', type);
return false;
}
// Some more ideas, do want anything like this ?
// - allow only specific scopes (eg. fix(docs) should not be allowed ?
// - auto correct the type to lower case ?
// - auto correct first letter of the subject to lower case ?
// - auto add empty line after subject ?
// - auto remove empty () ?
// - auto correct typos in type ?
// - store incorrect messages, so that we can learn
return isValid;
};
var firstLineFromBuffer = function(buffer) {
return buffer.toString().split('\n').shift();
};
// publish for testing
exports.validateMessage = validateMessage;
// hacky start if not run by jasmine :-D
if (process.argv.join('').indexOf('jasmine-node') === -1) {
var commitMsgFile = process.argv[2];
var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');
fs.readFile(commitMsgFile, function(err, buffer) {
var msg = firstLineFromBuffer(buffer);
if (!validateMessage(msg)) {
fs.appendFile(incorrectLogFile, msg + '\n', function() {
process.exit(1);
});
} else {
process.exit(0);
}
});
}

View file

@ -1,71 +0,0 @@
describe('validate-commit-msg.js', function() {
var m = require('./validate-commit-msg');
var errors = [];
var logs = [];
var VALID = true;
var INVALID = false;
beforeEach(function() {
errors.length = 0;
logs.length = 0;
spyOn(console, 'error').andCallFake(function(msg) {
errors.push(msg.replace(/\x1B\[\d+m/g, '')); // uncolor
});
spyOn(console, 'log').andCallFake(function(msg) {
logs.push(msg.replace(/\x1B\[\d+m/g, '')); // uncolor
});
});
describe('validateMessage', function() {
it('should be valid', function() {
expect(m.validateMessage('fixup! fix($compile): something')).toBe(VALID);
expect(m.validateMessage('fix($compile): something')).toBe(VALID);
expect(m.validateMessage('feat($location): something')).toBe(VALID);
expect(m.validateMessage('docs($filter): something')).toBe(VALID);
expect(m.validateMessage('style($http): something')).toBe(VALID);
expect(m.validateMessage('refactor($httpBackend): something')).toBe(VALID);
expect(m.validateMessage('test($resource): something')).toBe(VALID);
expect(m.validateMessage('chore($controller): something')).toBe(VALID);
expect(m.validateMessage('chore(foo-bar): something')).toBe(VALID);
expect(m.validateMessage('chore(*): something')).toBe(VALID);
expect(m.validateMessage('chore(guide/location): something')).toBe(VALID);
expect(m.validateMessage('revert(foo): something')).toBe(VALID);
expect(m.validateMessage('i18n(translate): something')).toBe(VALID);
expect(m.validateMessage('interface(ui-change): something')).toBe(VALID);
expect(errors).toEqual([]);
});
it('should validate "<type>(<scope>): <subject>" format', function() {
var msg = 'not correct format';
expect(m.validateMessage(msg)).toBe(INVALID);
expect(errors).toEqual(['INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" ! was: not correct format']);
});
it('should validate type', function() {
expect(m.validateMessage('weird($filter): something')).toBe(INVALID);
expect(errors).toEqual(['INVALID COMMIT MSG: "weird" is not allowed type !']);
});
it('should allow empty scope', function() {
expect(m.validateMessage('fix: blablabla')).toBe(VALID);
});
it('should allow dot in scope', function() {
expect(m.validateMessage('chore(mocks.$httpBackend): something')).toBe(VALID);
});
it('should ignore msg prefixed with "WIP: "', function() {
expect(m.validateMessage('WIP: bullshit')).toBe(VALID);
});
});
});