mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 07:37:25 +01:00
gMerge branch 'challenges' into develop
Conflicts: migrations/20131028_cleanup_deleted_tags.js src/controllers/groups.js views/options/groups/group.jade views/options/profile.jade
This commit is contained in:
@@ -49,6 +49,7 @@ module.exports = function(grunt) {
|
|||||||
'public/js/services/groupServices.js',
|
'public/js/services/groupServices.js',
|
||||||
'public/js/services/memberServices.js',
|
'public/js/services/memberServices.js',
|
||||||
'public/js/services/guideServices.js',
|
'public/js/services/guideServices.js',
|
||||||
|
'public/js/services/challengeServices.js',
|
||||||
|
|
||||||
'public/js/filters/filters.js',
|
'public/js/filters/filters.js',
|
||||||
|
|
||||||
@@ -61,14 +62,14 @@ module.exports = function(grunt) {
|
|||||||
'public/js/controllers/settingsCtrl.js',
|
'public/js/controllers/settingsCtrl.js',
|
||||||
'public/js/controllers/statsCtrl.js',
|
'public/js/controllers/statsCtrl.js',
|
||||||
'public/js/controllers/tasksCtrl.js',
|
'public/js/controllers/tasksCtrl.js',
|
||||||
'public/js/controllers/taskDetailsCtrl.js',
|
|
||||||
'public/js/controllers/filtersCtrl.js',
|
'public/js/controllers/filtersCtrl.js',
|
||||||
'public/js/controllers/userCtrl.js',
|
'public/js/controllers/userCtrl.js',
|
||||||
'public/js/controllers/groupsCtrl.js',
|
'public/js/controllers/groupsCtrl.js',
|
||||||
'public/js/controllers/petsCtrl.js',
|
'public/js/controllers/petsCtrl.js',
|
||||||
'public/js/controllers/inventoryCtrl.js',
|
'public/js/controllers/inventoryCtrl.js',
|
||||||
'public/js/controllers/marketCtrl.js',
|
'public/js/controllers/marketCtrl.js',
|
||||||
'public/js/controllers/footerCtrl.js'
|
'public/js/controllers/footerCtrl.js',
|
||||||
|
'public/js/controllers/challengesCtrl.js'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,83 +1,239 @@
|
|||||||
_ = require 'lodash'
|
_ = require 'lodash'
|
||||||
helpers = require 'habitrpg-shared/script/helpers'
|
{helpers} = require 'habitrpg-shared'
|
||||||
|
async = require 'async'
|
||||||
|
|
||||||
module.exports.app = (appExports, model) ->
|
module.exports.app = (app) ->
|
||||||
browser = require './browser'
|
|
||||||
user = model.at '_user'
|
|
||||||
|
|
||||||
$('#profile-challenges-tab-link').on 'show', (e) ->
|
###
|
||||||
_.each model.get('groups'), (g) ->
|
Sync any updates to challenges since last refresh. Do it after cron, so we don't punish them for new tasks
|
||||||
_.each g.challenges, (chal) ->
|
This is challenge->user sync. user->challenge happens when user interacts with their tasks
|
||||||
_.each ['habit','daily','todo'], (type) ->
|
###
|
||||||
_.each chal["#{type}s"], (task) ->
|
app.on 'ready', (model) ->
|
||||||
_.each chal.users, (member) ->
|
window.setTimeout ->
|
||||||
if (history = member?["#{type}s"]?[task.id]?.history) and !!history
|
_.each model.get('groups'), (g) ->
|
||||||
data = google.visualization.arrayToDataTable _.map(history, (h)-> [h.date,h.value])
|
if (@uid in g.members) and g.challenges
|
||||||
options =
|
_.each(g.challenges, ->app.challenges.syncChalToUser g)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
appExports.challengeCreate = (e,el) ->
|
|
||||||
[type, gid] = [$(el).attr('data-type'), $(el).attr('data-gid')]
|
|
||||||
model.set '_challenge.new',
|
|
||||||
name: ''
|
|
||||||
habits: []
|
|
||||||
dailys: []
|
|
||||||
todos: []
|
|
||||||
rewards: []
|
|
||||||
id: model.id()
|
|
||||||
uid: user.get('id')
|
|
||||||
user: helpers.username(model.get('_user.auth'), model.get('_user.profile.name'))
|
|
||||||
group: {type, id:gid}
|
|
||||||
timestamp: +new Date
|
|
||||||
|
|
||||||
appExports.challengeSave = ->
|
|
||||||
gid = model.get('_challenge.new.group.id')
|
|
||||||
model.unshift "groups.#{gid}.challenges", model.get('_challenge.new'), ->
|
|
||||||
browser.growlNotification('Challenge Created','success')
|
|
||||||
challengeDiscard()
|
|
||||||
|
|
||||||
appExports.toggleChallengeEdit = (e, el) ->
|
|
||||||
path = "_editing.challenges.#{$(el).attr('data-id')}"
|
|
||||||
model.set path, !model.get(path)
|
|
||||||
|
|
||||||
appExports.challengeDiscard = challengeDiscard = -> model.del '_challenge.new'
|
|
||||||
|
|
||||||
appExports.challengeSubscribe = (e) ->
|
|
||||||
chal = e.get()
|
|
||||||
|
|
||||||
# Add challenge name as a tag for user
|
|
||||||
tags = user.get('tags')
|
|
||||||
unless tags and _.find(tags,{id: chal.id})
|
|
||||||
model.push '_user.tags', {id: chal.id, name: chal.name, challenge: true}
|
|
||||||
|
|
||||||
tags = {}; tags[chal.id] = true
|
|
||||||
# Add all challenge's tasks to user's tasks
|
|
||||||
userChallenges = user.get('challenges')
|
|
||||||
user.unshift('challenges', chal.id) unless userChallenges and (userChallenges.indexOf(chal.id) != -1)
|
|
||||||
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
|
|
||||||
_.each chal["#{type}s"], (task) ->
|
|
||||||
task.tags = tags
|
|
||||||
task.challenge = chal.id
|
|
||||||
task.group = {id: chal.group.id, type: chal.group.type}
|
|
||||||
model.push("_#{type}List", task)
|
|
||||||
true
|
true
|
||||||
|
, 500
|
||||||
|
|
||||||
appExports.challengeUnsubscribe = (e) ->
|
###
|
||||||
chal = e.get()
|
Sync user to challenge (when they score, add to statistics)
|
||||||
i = user.get('challenges')?.indexOf chal.id
|
###
|
||||||
user.remove("challenges.#{i}") if i? and i != -1
|
app.model.on "change", "_page.user.priv.tasks.*.value", (id, value, previous, passed) ->
|
||||||
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
|
### Sync to challenge, but do it later ###
|
||||||
_.each chal["#{type}s"], (task) ->
|
async.nextTick =>
|
||||||
model.remove "_#{type}List", _.findIndex(model.get("_#{type}List",{id:task.id}))
|
model = app.model
|
||||||
model.del "_user.tasks.#{task.id}"
|
ctx = {model: model}
|
||||||
true
|
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')
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"angular-resource": "1.2.0-rc.2",
|
"angular-resource": "1.2.0-rc.2",
|
||||||
"angular-ui": "~0.4.0",
|
"angular-ui": "~0.4.0",
|
||||||
"angular-bootstrap": "~0.5.0",
|
"angular-bootstrap": "~0.5.0",
|
||||||
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git#rewrite",
|
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git#challenges",
|
||||||
"bootstrap-tour": "0.5.0",
|
"bootstrap-tour": "0.5.0",
|
||||||
"BrowserQuest": "https://github.com/mozilla/BrowserQuest.git",
|
"BrowserQuest": "https://github.com/mozilla/BrowserQuest.git",
|
||||||
"github-buttons": "git://github.com/mdo/github-buttons.git",
|
"github-buttons": "git://github.com/mdo/github-buttons.git",
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
"sticky": "*",
|
"sticky": "*",
|
||||||
"bootstrap-datepicker": "~1.2.0",
|
"bootstrap-datepicker": "~1.2.0",
|
||||||
"bootstrap": "v2.3.2",
|
"bootstrap": "v2.3.2",
|
||||||
"angular-route": "1.2.0-rc.2",
|
|
||||||
"angular-ui-utils": "~0.0.4",
|
"angular-ui-utils": "~0.0.4",
|
||||||
"angular-sanitize": "1.2.0-rc.2",
|
"angular-sanitize": "1.2.0-rc.2",
|
||||||
"marked": "~0.2.9",
|
"marked": "~0.2.9",
|
||||||
"JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master"
|
"JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master",
|
||||||
|
"angular-ui-router": "eda67d2da08cbe2aa1aa39ef154a87c7afe480ec"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"jquery": "~2.0.3",
|
"jquery": "~2.0.3",
|
||||||
|
|||||||
45
migrations/20131028_task_subdocs_and_tags_cleanup.js
Normal file
45
migrations/20131028_task_subdocs_and_tags_cleanup.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
db.users.find().forEach(function(user){
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Migrate to TaskSchema subdocs!!
|
||||||
|
if (!user.tasks) {
|
||||||
|
printjson(user.auth);
|
||||||
|
// FIXME before deploying!
|
||||||
|
} 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];
|
||||||
|
if (!task) return m; // remove null tasks
|
||||||
|
//if (!user.tasks[tid].tags) user.tasks[tid].tags = {}; // shouldn't be necessary, since TaskSchema.tags has default {}
|
||||||
|
task._id = task.id;
|
||||||
|
m.push(task);
|
||||||
|
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
|
||||||
|
db.groups.update({},{$pull:{challenges:1}},{multi:true});
|
||||||
|
db.groups.find().forEach(function(group){
|
||||||
|
db.groups.update({_id:group._id}, {
|
||||||
|
$set:{memberCount: _.size(group.members)}
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"version": "0.0.0-152",
|
"version": "0.0.0-152",
|
||||||
"main": "./src/server.js",
|
"main": "./src/server.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#rewrite",
|
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#challenges",
|
||||||
"derby-auth": "git://github.com/lefnire/derby-auth#master",
|
"derby-auth": "git://github.com/lefnire/derby-auth#master",
|
||||||
"connect-mongo": "*",
|
"connect-mongo": "*",
|
||||||
"passport-facebook": "~1.0.0",
|
"passport-facebook": "~1.0.0",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"lodash": "~1.3.1",
|
"lodash": "~1.3.1",
|
||||||
"async": "~0.2.9",
|
"async": "~0.2.9",
|
||||||
"optimist": "~0.5.2",
|
"optimist": "~0.5.2",
|
||||||
"mongoose": "~3.6.18",
|
"mongoose": "~3.6.20",
|
||||||
"stylus": "~0.37.0",
|
"stylus": "~0.37.0",
|
||||||
"grunt": "~0.4.1",
|
"grunt": "~0.4.1",
|
||||||
"grunt-contrib-uglify": "~0.2.4",
|
"grunt-contrib-uglify": "~0.2.4",
|
||||||
|
|||||||
128
public/js/app.js
128
public/js/app.js
@@ -1,21 +1,133 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
window.habitrpg = angular.module('habitrpg',
|
window.habitrpg = angular.module('habitrpg',
|
||||||
['ngRoute', 'ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'sharedServices', 'authServices', 'notificationServices', 'guideServices', 'ui.bootstrap', 'ui.keypress'])
|
['ngRoute', 'ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices', 'sharedServices', 'authServices', 'notificationServices', 'guideServices', 'ui.bootstrap', 'ui.keypress', 'ui.router'])
|
||||||
|
|
||||||
.constant("API_URL", "")
|
.constant("API_URL", "")
|
||||||
.constant("STORAGE_USER_ID", 'habitrpg-user')
|
.constant("STORAGE_USER_ID", 'habitrpg-user')
|
||||||
.constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings')
|
.constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings')
|
||||||
//.constant("STORAGE_GROUPS_ID", "") // if we decide to take groups offline
|
//.constant("STORAGE_GROUPS_ID", "") // if we decide to take groups offline
|
||||||
|
|
||||||
.config(['$routeProvider', '$httpProvider', 'STORAGE_SETTINGS_ID',
|
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'STORAGE_SETTINGS_ID',
|
||||||
function($routeProvider, $httpProvider, STORAGE_SETTINGS_ID) {
|
function($stateProvider, $urlRouterProvider, $httpProvider, STORAGE_SETTINGS_ID) {
|
||||||
$routeProvider
|
|
||||||
//.when('/login', {templateUrl: 'views/login.html'})
|
|
||||||
.when('/tasks', {templateUrl: 'partials/tasks'})
|
|
||||||
.when('/options', {templateUrl: 'partials/options'})
|
|
||||||
|
|
||||||
.otherwise({redirectTo: '/tasks'});
|
$urlRouterProvider
|
||||||
|
// Setup default selected tabs
|
||||||
|
.when('/options', '/options/profile/avatar')
|
||||||
|
.when('/options/profile', '/options/profile/avatar')
|
||||||
|
.when('/options/groups', '/options/groups/tavern')
|
||||||
|
.when('/options/groups/guilds', '/options/groups/guilds/public')
|
||||||
|
.when('/options/inventory', '/options/inventory/inventory')
|
||||||
|
|
||||||
|
// redirect states that don't match
|
||||||
|
.otherwise("/tasks");
|
||||||
|
|
||||||
|
$stateProvider
|
||||||
|
|
||||||
|
// Tasks
|
||||||
|
.state('tasks', {
|
||||||
|
url: "/tasks",
|
||||||
|
templateUrl: "partials/main.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options
|
||||||
|
.state('options', {
|
||||||
|
url: "/options",
|
||||||
|
templateUrl: "partials/options.html",
|
||||||
|
controller: function(){}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options > Profile
|
||||||
|
.state('options.profile', {
|
||||||
|
url: "/profile",
|
||||||
|
templateUrl: "partials/options.profile.html",
|
||||||
|
controller: 'UserCtrl'
|
||||||
|
})
|
||||||
|
.state('options.profile.avatar', {
|
||||||
|
url: "/avatar",
|
||||||
|
templateUrl: "partials/options.profile.avatar.html"
|
||||||
|
})
|
||||||
|
.state('options.profile.stats', {
|
||||||
|
url: "/stats",
|
||||||
|
templateUrl: "partials/options.profile.stats.html"
|
||||||
|
})
|
||||||
|
.state('options.profile.profile', {
|
||||||
|
url: "/stats",
|
||||||
|
templateUrl: "partials/options.profile.profile.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options > Groups
|
||||||
|
.state('options.groups', {
|
||||||
|
url: "/groups",
|
||||||
|
templateUrl: "partials/options.groups.html"
|
||||||
|
})
|
||||||
|
.state('options.groups.tavern', {
|
||||||
|
url: "/tavern",
|
||||||
|
templateUrl: "partials/options.groups.tavern.html",
|
||||||
|
controller: 'TavernCtrl'
|
||||||
|
})
|
||||||
|
.state('options.groups.party', {
|
||||||
|
url: '/party',
|
||||||
|
templateUrl: "partials/options.groups.party.html",
|
||||||
|
controller: 'PartyCtrl'
|
||||||
|
})
|
||||||
|
.state('options.groups.guilds', {
|
||||||
|
url: '/guilds',
|
||||||
|
templateUrl: "partials/options.groups.guilds.html",
|
||||||
|
controller: 'GuildsCtrl'
|
||||||
|
})
|
||||||
|
.state('options.groups.guilds.public', {
|
||||||
|
url: '/public',
|
||||||
|
templateUrl: "partials/options.groups.guilds.public.html"
|
||||||
|
})
|
||||||
|
.state('options.groups.guilds.create', {
|
||||||
|
url: '/create',
|
||||||
|
templateUrl: "partials/options.groups.guilds.create.html"
|
||||||
|
})
|
||||||
|
.state('options.groups.guilds.detail', {
|
||||||
|
url: '/:gid',
|
||||||
|
templateUrl: 'partials/options.groups.guilds.detail.html',
|
||||||
|
controller: ['$scope', 'Groups', '$stateParams', function($scope, Groups, $stateParams){
|
||||||
|
$scope.group = Groups.Group.get({gid:$stateParams.gid});
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options > Inventory
|
||||||
|
.state('options.inventory', {
|
||||||
|
url: '/inventory',
|
||||||
|
templateUrl: "partials/options.inventory.html"
|
||||||
|
})
|
||||||
|
.state('options.inventory.inventory', {
|
||||||
|
url: '/inventory',
|
||||||
|
templateUrl: "partials/options.inventory.inventory.html"
|
||||||
|
})
|
||||||
|
.state('options.inventory.stable', {
|
||||||
|
url: '/stable',
|
||||||
|
templateUrl: "partials/options.inventory.stable.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options > Challenges
|
||||||
|
.state('options.challenges', {
|
||||||
|
url: "/challenges",
|
||||||
|
controller: 'ChallengesCtrl',
|
||||||
|
templateUrl: "partials/options.challenges.html",
|
||||||
|
resolve: {
|
||||||
|
groups: ['$http', 'API_URL', function($http, API_URL){
|
||||||
|
// TODO come up with more unified ngResource-style approach
|
||||||
|
return $http.get(API_URL + '/api/v1/groups?minimal=true');
|
||||||
|
}],
|
||||||
|
challenges: ['Challenges', function(Challenges){
|
||||||
|
return Challenges.Challenge.query();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options > Settings
|
||||||
|
.state('options.settings', {
|
||||||
|
url: "/settings",
|
||||||
|
controller: 'SettingsCtrl',
|
||||||
|
templateUrl: "partials/options.settings.html"
|
||||||
|
})
|
||||||
|
|
||||||
var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID));
|
var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID));
|
||||||
if (settings && settings.auth) {
|
if (settings && settings.auth) {
|
||||||
|
|||||||
122
public/js/controllers/challengesCtrl.js
Normal file
122
public/js/controllers/challengesCtrl.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notification', '$compile', 'groups', 'challenges',
|
||||||
|
function($scope, User, Challenges, Notification, $compile, groups, challenges) {
|
||||||
|
|
||||||
|
// groups & challenges are loaded as `resolve` via ui-router (see app.js)
|
||||||
|
$scope.groups = groups;
|
||||||
|
$scope.challenges = challenges;
|
||||||
|
|
||||||
|
//------------------------------------------------------------
|
||||||
|
// Challenge
|
||||||
|
//------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create
|
||||||
|
*/
|
||||||
|
$scope.create = function() {
|
||||||
|
$scope.newChallenge = new Challenges.Challenge({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
habits: [],
|
||||||
|
dailys: [],
|
||||||
|
todos: [],
|
||||||
|
rewards: [],
|
||||||
|
leader: User.user._id,
|
||||||
|
group: null,
|
||||||
|
timestamp: +(new Date),
|
||||||
|
members: []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save
|
||||||
|
*/
|
||||||
|
$scope.save = function(challenge) {
|
||||||
|
if (!challenge.group) return alert('Please select group');
|
||||||
|
var isNew = !challenge._id;
|
||||||
|
challenge.$save(function(){
|
||||||
|
if (isNew) {
|
||||||
|
Notification.text('Challenge Created');
|
||||||
|
$scope.discard();
|
||||||
|
Challenges.Challenge.query();
|
||||||
|
} else {
|
||||||
|
// TODO figure out a more elegant way about this
|
||||||
|
//challenge._editing = false;
|
||||||
|
challenge._locked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discard
|
||||||
|
*/
|
||||||
|
$scope.discard = function() {
|
||||||
|
$scope.newChallenge = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete
|
||||||
|
*/
|
||||||
|
$scope["delete"] = function(challenge) {
|
||||||
|
if (confirm("Delete challenge, are you sure?") !== true) return;
|
||||||
|
challenge.$delete();
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------------
|
||||||
|
// Tasks
|
||||||
|
//------------------------------------------------------------
|
||||||
|
|
||||||
|
$scope.addTask = function(list) {
|
||||||
|
var task = window.habitrpgShared.helpers.taskDefaults({text: list.newTask, type: list.type}, User.user.filters);
|
||||||
|
list.tasks.unshift(task);
|
||||||
|
//User.log({op: "addTask", data: task}); //TODO persist
|
||||||
|
delete list.newTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeTask = function(list, $index) {
|
||||||
|
if (confirm("Are you sure you want to delete this task?")) return;
|
||||||
|
//TODO persist
|
||||||
|
// User.log({
|
||||||
|
// op: "delTask",
|
||||||
|
// data: task
|
||||||
|
//});
|
||||||
|
list.splice($index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.saveTask = function(task){
|
||||||
|
task._editing = false;
|
||||||
|
// TODO persist
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
--------------------------
|
||||||
|
Unsubscribe functions
|
||||||
|
--------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
$scope.unsubscribe = function(keep) {
|
||||||
|
if (keep == 'cancel') {
|
||||||
|
$scope.selectedChal = undefined;
|
||||||
|
} else {
|
||||||
|
$scope.selectedChal.$leave({keep:keep});
|
||||||
|
}
|
||||||
|
$scope.popoverEl.popover('destroy');
|
||||||
|
}
|
||||||
|
$scope.clickUnsubscribe = function(chal, $event) {
|
||||||
|
$scope.selectedChal = chal;
|
||||||
|
$scope.popoverEl = $($event.target);
|
||||||
|
var html = $compile(
|
||||||
|
'<a ng-controller="ChallengesCtrl" ng-click="unsubscribe(\'remove-all\')">Remove Tasks</a><br/>\n<a ng-click="unsubscribe(\'keep-all\')">Keep Tasks</a><br/>\n<a ng-click="unsubscribe(\'cancel\')">Cancel</a><br/>'
|
||||||
|
)($scope);
|
||||||
|
$scope.popoverEl.popover('destroy').popover({
|
||||||
|
html: true,
|
||||||
|
placement: 'top',
|
||||||
|
trigger: 'manual',
|
||||||
|
title: 'Unsubscribe From Challenge And:',
|
||||||
|
content: html
|
||||||
|
}).popover('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
}]);
|
||||||
@@ -34,7 +34,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'API_URL', '
|
|||||||
delete user.filters[tag.id];
|
delete user.filters[tag.id];
|
||||||
user.tags.splice($index,1);
|
user.tags.splice($index,1);
|
||||||
// remove tag from all tasks
|
// remove tag from all tasks
|
||||||
_.each(user.tasks, function(task) {
|
_.each(user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards), function(task) {
|
||||||
delete task.tags[tag.id];
|
delete task.tags[tag.id];
|
||||||
});
|
});
|
||||||
User.log({op:'delTag',data:{'tag':tag.id}})
|
User.log({op:'delTag',data:{'tag':tag.id}})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'API_URL', '$q', 'User', 'Members', '$location',
|
habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'API_URL', '$q', 'User', 'Members', '$location', '$state',
|
||||||
function($scope, $rootScope, Groups, $http, API_URL, $q, User, Members, $location) {
|
function($scope, $rootScope, Groups, $http, API_URL, $q, User, Members, $location, $state) {
|
||||||
|
|
||||||
$scope.isMember = function(user, group){
|
$scope.isMember = function(user, group){
|
||||||
return ~(group.members.indexOf(user._id));
|
return ~(group.members.indexOf(user._id));
|
||||||
@@ -35,10 +35,10 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||||||
|
|
||||||
$scope.clickMember = function(uid, forceShow) {
|
$scope.clickMember = function(uid, forceShow) {
|
||||||
if (User.user._id == uid && !forceShow) {
|
if (User.user._id == uid && !forceShow) {
|
||||||
if ($location.path() == '/tasks') {
|
if ($state.is('tasks')) {
|
||||||
$location.path('/options');
|
$state.go('options');
|
||||||
} else {
|
} else {
|
||||||
$location.path('/tasks');
|
$state.go('tasks');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We need the member information up top here, but then we pass it down to the modal controller
|
// We need the member information up top here, but then we pass it down to the modal controller
|
||||||
@@ -128,6 +128,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||||||
|
|
||||||
.controller("GuildsCtrl", ['$scope', 'Groups', 'User', '$rootScope',
|
.controller("GuildsCtrl", ['$scope', 'Groups', 'User', '$rootScope',
|
||||||
function($scope, Groups, User, $rootScope) {
|
function($scope, Groups, User, $rootScope) {
|
||||||
|
Groups.fetchGuilds();
|
||||||
$scope.type = 'guild';
|
$scope.type = 'guild';
|
||||||
$scope.text = 'Guild';
|
$scope.text = 'Guild';
|
||||||
$scope.newGroup = new Groups.Group({type:'guild', privacy:'private', leader: User.user._id, members: [User.user._id]});
|
$scope.newGroup = new Groups.Group({type:'guild', privacy:'private', leader: User.user._id, members: [User.user._id]});
|
||||||
@@ -212,8 +213,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||||||
|
|
||||||
.controller("TavernCtrl", ['$scope', 'Groups', 'User',
|
.controller("TavernCtrl", ['$scope', 'Groups', 'User',
|
||||||
function($scope, Groups, User) {
|
function($scope, Groups, User) {
|
||||||
|
Groups.fetchTavern();
|
||||||
$scope.group = Groups.groups.tavern;
|
$scope.group = Groups.groups.tavern;
|
||||||
|
|
||||||
$scope.rest = function(){
|
$scope.rest = function(){
|
||||||
User.user.flags.rest = !User.user.flags.rest;
|
User.user.flags.rest = !User.user.flags.rest;
|
||||||
User.log({op:'set',data:{'flags.rest':User.user.flags.rest}});
|
User.log({op:'set',data:{'flags.rest':User.user.flags.rest}});
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
/* Make user and settings available for everyone through root scope.
|
/* Make user and settings available for everyone through root scope.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http',
|
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams',
|
||||||
function($scope, $rootScope, $location, User, $http) {
|
function($scope, $rootScope, $location, User, $http, $state, $stateParams) {
|
||||||
$rootScope.modals = {};
|
$rootScope.modals = {};
|
||||||
$rootScope.modals.achievements = {};
|
$rootScope.modals.achievements = {};
|
||||||
$rootScope.User = User;
|
$rootScope.User = User;
|
||||||
@@ -12,6 +12,15 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
|||||||
$rootScope.settings = User.settings;
|
$rootScope.settings = User.settings;
|
||||||
$rootScope.flash = {errors: [], warnings: []};
|
$rootScope.flash = {errors: [], warnings: []};
|
||||||
|
|
||||||
|
// Angular UI Router
|
||||||
|
$rootScope.$state = $state;
|
||||||
|
$rootScope.$stateParams = $stateParams;
|
||||||
|
|
||||||
|
// indexOf helper
|
||||||
|
$scope.indexOf = function(haystack, needle){
|
||||||
|
return haystack && ~haystack.indexOf(needle);
|
||||||
|
}
|
||||||
|
|
||||||
$scope.safeApply = function(fn) {
|
$scope.safeApply = function(fn) {
|
||||||
var phase = this.$root.$$phase;
|
var phase = this.$root.$$phase;
|
||||||
if(phase == '$apply' || phase == '$digest') {
|
if(phase == '$apply' || phase == '$digest') {
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
habitrpg.controller("TaskDetailsCtrl", ['$scope', '$rootScope', '$location', 'User',
|
|
||||||
function($scope, $rootScope, $location, User) {
|
|
||||||
$scope.save = function(task) {
|
|
||||||
var log, setVal;
|
|
||||||
setVal = function(k, v) {
|
|
||||||
var op;
|
|
||||||
if (typeof v !== "undefined") {
|
|
||||||
op = {
|
|
||||||
op: "set",
|
|
||||||
data: {}
|
|
||||||
};
|
|
||||||
op.data["tasks." + task.id + "." + k] = v;
|
|
||||||
return log.push(op);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
log = [];
|
|
||||||
setVal("text", task.text);
|
|
||||||
setVal("notes", task.notes);
|
|
||||||
setVal("priority", task.priority);
|
|
||||||
setVal("tags", task.tags);
|
|
||||||
if (task.type === "habit") {
|
|
||||||
setVal("up", task.up);
|
|
||||||
setVal("down", task.down);
|
|
||||||
} else if (task.type === "daily") {
|
|
||||||
setVal("repeat", task.repeat);
|
|
||||||
// TODO we'll remove this once rewrite's running for a while. This was a patch for derby issues
|
|
||||||
setVal("streak", task.streak);
|
|
||||||
|
|
||||||
} else if (task.type === "todo") {
|
|
||||||
setVal("date", task.date);
|
|
||||||
} else {
|
|
||||||
if (task.type === "reward") {
|
|
||||||
setVal("value", task.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
User.log(log);
|
|
||||||
task._editing = false;
|
|
||||||
};
|
|
||||||
$scope.cancel = function() {
|
|
||||||
/* reset $scope.task to $scope.originalTask
|
|
||||||
*/
|
|
||||||
|
|
||||||
var key;
|
|
||||||
for (key in $scope.task) {
|
|
||||||
$scope.task[key] = $scope.originalTask[key];
|
|
||||||
}
|
|
||||||
$scope.originalTask = null;
|
|
||||||
$scope.editedTask = null;
|
|
||||||
$scope.editing = false;
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
@@ -1,36 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', 'Algos', 'Helpers', 'Notification',
|
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', 'Algos', 'Helpers', 'Notification', '$http', 'API_URL',
|
||||||
function($scope, $rootScope, $location, User, Algos, Helpers, Notification) {
|
function($scope, $rootScope, $location, User, Algos, Helpers, Notification, $http, API_URL) {
|
||||||
/*FIXME
|
|
||||||
*/
|
|
||||||
$scope.taskLists = [
|
|
||||||
{
|
|
||||||
header: 'Habits',
|
|
||||||
type: 'habit',
|
|
||||||
placeHolder: 'New Habit',
|
|
||||||
main: true,
|
|
||||||
editable: true
|
|
||||||
}, {
|
|
||||||
header: 'Dailies',
|
|
||||||
type: 'daily',
|
|
||||||
placeHolder: 'New Daily',
|
|
||||||
main: true,
|
|
||||||
editable: true
|
|
||||||
}, {
|
|
||||||
header: 'Todos',
|
|
||||||
type: 'todo',
|
|
||||||
placeHolder: 'New Todo',
|
|
||||||
main: true,
|
|
||||||
editable: true
|
|
||||||
}, {
|
|
||||||
header: 'Rewards',
|
|
||||||
type: 'reward',
|
|
||||||
placeHolder: 'New Reward',
|
|
||||||
main: true,
|
|
||||||
editable: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
$scope.score = function(task, direction) {
|
$scope.score = function(task, direction) {
|
||||||
if (task.type === "reward" && User.user.stats.gp < task.value){
|
if (task.type === "reward" && User.user.stats.gp < task.value){
|
||||||
return Notification.text('Not enough GP.');
|
return Notification.text('Not enough GP.');
|
||||||
@@ -42,18 +13,20 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
|
|||||||
|
|
||||||
$scope.addTask = function(list) {
|
$scope.addTask = function(list) {
|
||||||
var task = window.habitrpgShared.helpers.taskDefaults({text: list.newTask, type: list.type}, User.user.filters);
|
var task = window.habitrpgShared.helpers.taskDefaults({text: list.newTask, type: list.type}, User.user.filters);
|
||||||
User.user[list.type + "s"].unshift(task);
|
list.tasks.unshift(task);
|
||||||
// $scope.showedTasks.unshift newTask # FIXME what's thiss?
|
|
||||||
User.log({op: "addTask", data: task});
|
User.log({op: "addTask", data: task});
|
||||||
delete list.newTask;
|
delete list.newTask;
|
||||||
};
|
};
|
||||||
/*Add the new task to the actions log
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the new task to the actions log
|
||||||
|
*/
|
||||||
$scope.clearDoneTodos = function() {};
|
$scope.clearDoneTodos = function() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is calculated post-change, so task.completed=true if they just checked it
|
||||||
|
*/
|
||||||
$scope.changeCheck = function(task) {
|
$scope.changeCheck = function(task) {
|
||||||
/* This is calculated post-change, so task.completed=true if they just checked it
|
|
||||||
*/
|
|
||||||
if (task.completed) {
|
if (task.completed) {
|
||||||
$scope.score(task, "up");
|
$scope.score(task, "up");
|
||||||
} else {
|
} else {
|
||||||
@@ -66,27 +39,67 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
|
|||||||
// uhoh! our first name conflict with habitrpg-shared/helpers, we gotta resovle that soon.
|
// uhoh! our first name conflict with habitrpg-shared/helpers, we gotta resovle that soon.
|
||||||
$rootScope.clickRevive = function() {
|
$rootScope.clickRevive = function() {
|
||||||
window.habitrpgShared.algos.revive(User.user);
|
window.habitrpgShared.algos.revive(User.user);
|
||||||
User.log({
|
User.log({ op: "revive" });
|
||||||
op: "revive"
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.toggleEdit = function(task){
|
$scope.removeTask = function(list, $index) {
|
||||||
task._editing = !task._editing;
|
if (!confirm("Are you sure you want to delete this task?")) return;
|
||||||
if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false;
|
User.log({ op: "delTask", data: list[$index] });
|
||||||
|
list.splice($index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.remove = function(task) {
|
$scope.saveTask = function(task) {
|
||||||
var tasks;
|
var setVal = function(k, v) {
|
||||||
if (confirm("Are you sure you want to delete this task?") !== true) {
|
var op;
|
||||||
return;
|
if (typeof v !== "undefined") {
|
||||||
|
op = { op: "set", data: {} };
|
||||||
|
op.data["tasks." + task.id + "." + k] = v;
|
||||||
|
return log.push(op);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var log = [];
|
||||||
|
setVal("text", task.text);
|
||||||
|
setVal("notes", task.notes);
|
||||||
|
setVal("priority", task.priority);
|
||||||
|
setVal("tags", task.tags);
|
||||||
|
if (task.type === "habit") {
|
||||||
|
setVal("up", task.up);
|
||||||
|
setVal("down", task.down);
|
||||||
|
} else if (task.type === "daily") {
|
||||||
|
setVal("repeat", task.repeat);
|
||||||
|
// TODO we'll remove this once rewrite's running for a while. This was a patch for derby issues
|
||||||
|
setVal("streak", task.streak);
|
||||||
|
|
||||||
|
} else if (task.type === "todo") {
|
||||||
|
setVal("date", task.date);
|
||||||
|
} else {
|
||||||
|
if (task.type === "reward") {
|
||||||
|
setVal("value", task.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tasks = User.user[task.type + "s"];
|
User.log(log);
|
||||||
User.log({
|
task._editing = false;
|
||||||
op: "delTask",
|
};
|
||||||
data: task
|
|
||||||
});
|
/**
|
||||||
tasks.splice(tasks.indexOf(task), 1);
|
* Reset $scope.task to $scope.originalTask
|
||||||
|
*/
|
||||||
|
$scope.cancel = function() {
|
||||||
|
var key;
|
||||||
|
for (key in $scope.task) {
|
||||||
|
$scope.task[key] = $scope.originalTask[key];
|
||||||
|
}
|
||||||
|
$scope.originalTask = null;
|
||||||
|
$scope.editedTask = null;
|
||||||
|
$scope.editing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.unlink = function(task, keep) {
|
||||||
|
// TODO move this to userServices, turn userSerivces.user into ng-resource
|
||||||
|
$http.post(API_URL + '/api/v1/user/task/' + task.id + '/unlink?keep=' + keep)
|
||||||
|
.success(function(){
|
||||||
|
User.log({});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -123,4 +136,15 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
|
|||||||
User.log({op: 'clear-completed'});
|
User.log({op: 'clear-completed'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See conversation on http://productforums.google.com/forum/#!topic/adsense/WYkC_VzKwbA,
|
||||||
|
* Adsense is very sensitive. It must be called once-and-only-once for every <ins>, else things break.
|
||||||
|
* Additionally, angular won't run javascript embedded into a script template, so we can't copy/paste
|
||||||
|
* the html provided by adsense - we need to run this function post-link
|
||||||
|
*/
|
||||||
|
$scope.initAds = function(){
|
||||||
|
$.getScript('//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js');
|
||||||
|
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
||||||
|
}
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@@ -109,3 +109,51 @@ habitrpg.directive('habitrpgSortable', ['User', function(User) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
habitrpg
|
||||||
|
.directive('habitrpgTasks', ['$rootScope', 'User', function($rootScope, User) {
|
||||||
|
return {
|
||||||
|
restrict: 'EA',
|
||||||
|
templateUrl: 'templates/habitrpg-tasks.html',
|
||||||
|
//transclude: true,
|
||||||
|
//scope: {
|
||||||
|
// main: '@', // true if it's the user's main list
|
||||||
|
// obj: '='
|
||||||
|
//},
|
||||||
|
controller: function($scope, $rootScope){
|
||||||
|
$scope.editTask = function(task){
|
||||||
|
task._editing = !task._editing;
|
||||||
|
if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
link: function(scope, element, attrs) {
|
||||||
|
scope.obj = scope[attrs.obj];
|
||||||
|
scope.main = attrs.main;
|
||||||
|
|
||||||
|
scope.lists = [
|
||||||
|
{
|
||||||
|
header: 'Habits',
|
||||||
|
type: 'habit',
|
||||||
|
placeHolder: 'New Habit',
|
||||||
|
tasks: scope.obj.habits
|
||||||
|
}, {
|
||||||
|
header: 'Dailies',
|
||||||
|
type: 'daily',
|
||||||
|
placeHolder: 'New Daily',
|
||||||
|
tasks: scope.obj.dailys
|
||||||
|
}, {
|
||||||
|
header: 'Todos',
|
||||||
|
type: 'todo',
|
||||||
|
placeHolder: 'New Todo',
|
||||||
|
tasks: scope.obj.todos
|
||||||
|
}, {
|
||||||
|
header: 'Rewards',
|
||||||
|
type: 'reward',
|
||||||
|
placeHolder: 'New Reward',
|
||||||
|
tasks: scope.obj.rewards
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
|||||||
25
public/js/services/challengeServices.js
Normal file
25
public/js/services/challengeServices.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Services that persists and retrieves user from localStorage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
angular.module('challengeServices', ['ngResource']).
|
||||||
|
factory('Challenges', ['API_URL', '$resource', 'User', '$q', 'Members',
|
||||||
|
function(API_URL, $resource, User, $q, Members) {
|
||||||
|
var Challenge = $resource(API_URL + '/api/v1/challenges/:cid',
|
||||||
|
{cid:'@_id'},
|
||||||
|
{
|
||||||
|
//'query': {method: "GET", isArray:false}
|
||||||
|
join: {method: "POST", url: API_URL + '/api/v1/challenges/:cid/join'},
|
||||||
|
leave: {method: "POST", url: API_URL + '/api/v1/challenges/:cid/leave'}
|
||||||
|
});
|
||||||
|
|
||||||
|
//var challenges = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
Challenge: Challenge
|
||||||
|
//challenges: challenges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
@@ -10,7 +10,7 @@ angular.module('groupServices', ['ngResource']).
|
|||||||
var Group = $resource(API_URL + '/api/v1/groups/:gid',
|
var Group = $resource(API_URL + '/api/v1/groups/:gid',
|
||||||
{gid:'@_id', messageId: '@_messageId'},
|
{gid:'@_id', messageId: '@_messageId'},
|
||||||
{
|
{
|
||||||
//'query': {method: "GET", isArray:false}
|
query: {method: "GET", isArray:false},
|
||||||
postChat: {method: "POST", url: API_URL + '/api/v1/groups/:gid/chat'},
|
postChat: {method: "POST", url: API_URL + '/api/v1/groups/:gid/chat'},
|
||||||
deleteChatMessage: {method: "DELETE", url: API_URL + '/api/v1/groups/:gid/chat/:messageId'},
|
deleteChatMessage: {method: "DELETE", url: API_URL + '/api/v1/groups/:gid/chat/:messageId'},
|
||||||
join: {method: "POST", url: API_URL + '/api/v1/groups/:gid/join'},
|
join: {method: "POST", url: API_URL + '/api/v1/groups/:gid/join'},
|
||||||
@@ -34,32 +34,24 @@ angular.module('groupServices', ['ngResource']).
|
|||||||
};
|
};
|
||||||
|
|
||||||
// But we don't defer triggering Party, since we always need it for the header if nothing else
|
// But we don't defer triggering Party, since we always need it for the header if nothing else
|
||||||
Group.query({type:'party'}, function(_groups){
|
Group.get({gid:'party'}, function(party){
|
||||||
partyQ.resolve(_groups[0]);
|
partyQ.resolve(party);
|
||||||
Members.populate(_groups[0]);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Note the _.once() to make sure it can never be called again
|
// Note the _.once() to make sure it can never be called again
|
||||||
fetchGuilds: _.once(function(){
|
fetchGuilds: _.once(function(){
|
||||||
$('#loading-indicator').show();
|
Group.query(function(_groups){
|
||||||
Group.query({type:'guilds'}, function(_groups){
|
guildsQ.resolve(_groups.guilds);
|
||||||
guildsQ.resolve(_groups);
|
Members.populate(_groups.guilds);
|
||||||
Members.populate(_groups);
|
publicQ.resolve(_groups['public']);
|
||||||
$('#loading-indicator').hide();
|
Members.populate(_groups['public']);
|
||||||
})
|
|
||||||
Group.query({type:'public'}, function(_groups){
|
|
||||||
publicQ.resolve(_groups);
|
|
||||||
Members.populate(_groups);
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fetchTavern: _.once(function(){
|
fetchTavern: _.once(function(){
|
||||||
$('#loading-indicator').show();
|
Group.get({gid:'habitrpg'}, function(tavern){
|
||||||
Group.query({type:'tavern'}, function(_groups){
|
tavernQ.resolve(tavern);
|
||||||
$('#loading-indicator').hide();
|
|
||||||
tavernQ.resolve(_groups[0]);
|
|
||||||
Members.populate(_groups[0]);
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ angular.module('userServices', []).
|
|||||||
|
|
||||||
$http.post(API_URL + '/api/v1/user/batch-update', sent, {params: {data:+new Date, _v:user._v}})
|
$http.post(API_URL + '/api/v1/user/batch-update', sent, {params: {data:+new Date, _v:user._v}})
|
||||||
.success(function (data, status, heacreatingders, config) {
|
.success(function (data, status, heacreatingders, config) {
|
||||||
data.tasks = _.toArray(data.tasks);
|
|
||||||
//make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data.
|
//make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data.
|
||||||
if (!queue.length) {
|
if (!queue.length) {
|
||||||
//we can't do user=data as it will not update user references in all other angular controllers.
|
//we can't do user=data as it will not update user references in all other angular controllers.
|
||||||
|
|||||||
258
src/controllers/challenges.js
Normal file
258
src/controllers/challenges.js
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
// @see ../routes for routing
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
|
var nconf = require('nconf');
|
||||||
|
var async = require('async');
|
||||||
|
var algos = require('habitrpg-shared/script/algos');
|
||||||
|
var helpers = require('habitrpg-shared/script/helpers');
|
||||||
|
var items = require('habitrpg-shared/script/items');
|
||||||
|
var User = require('./../models/user').model;
|
||||||
|
var Group = require('./../models/group').model;
|
||||||
|
var Challenge = require('./../models/challenge').model;
|
||||||
|
var api = module.exports;
|
||||||
|
|
||||||
|
/*
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
Challenges
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GET
|
||||||
|
api.get = function(req, res) {
|
||||||
|
var user = res.locals.user;
|
||||||
|
Challenge.find({$or:[{leader: user._id}, {members:{$in:[user._id]}}]})
|
||||||
|
.populate('members', 'profile.name habits dailys rewards todos')
|
||||||
|
.exec(function(err, challenges){
|
||||||
|
if(err) return res.json(500, {err:err});
|
||||||
|
|
||||||
|
// slim down the return members' tasks to only the ones in the challenge
|
||||||
|
_.each(challenges, function(challenge){
|
||||||
|
_.each(challenge.members, function(member){
|
||||||
|
_.each(['habits', 'dailys', 'todos', 'rewards'], function(type){
|
||||||
|
member[type] = _.where(member[type], function(task){
|
||||||
|
return task.challenge && task.challenge.id == challenge._id;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(challenges);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE
|
||||||
|
api.create = function(req, res){
|
||||||
|
// FIXME sanitize
|
||||||
|
var challenge = new Challenge(req.body);
|
||||||
|
challenge.save(function(err, saved){
|
||||||
|
// Need to create challenge with refs (group, leader)? Or is this taken care of automatically?
|
||||||
|
// @see http://mongoosejs.com/docs/populate.html
|
||||||
|
if (err) return res.json(500, {err:err});
|
||||||
|
res.json(saved);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function keepAttrs(task) {
|
||||||
|
// only sync/compare important attrs
|
||||||
|
var keepAttrs = 'text notes up down priority repeat'.split(' ');
|
||||||
|
if (task.type=='reward') keepAttrs.push('value');
|
||||||
|
return _.pick(task, keepAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE
|
||||||
|
api.update = function(req, res){
|
||||||
|
//FIXME sanitize
|
||||||
|
var cid = req.params.cid;
|
||||||
|
async.waterfall([
|
||||||
|
function(cb){
|
||||||
|
// We first need the original challenge data, since we're going to compare against new & decide to sync users
|
||||||
|
Challenge.findById(cid, cb);
|
||||||
|
},
|
||||||
|
function(chal, cb) {
|
||||||
|
|
||||||
|
// Update the challenge, and then just res.json success (note we're passing `cb` here).
|
||||||
|
// The syncing stuff is really heavy, and the client doesn't care - so we kick it off in the background
|
||||||
|
delete req.body._id;
|
||||||
|
Challenge.findByIdAndUpdate(cid, {$set:req.body}, cb);
|
||||||
|
|
||||||
|
// Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers
|
||||||
|
function comparableData(obj) {
|
||||||
|
return (
|
||||||
|
_.chain(obj.habits.concat(obj.dailys).concat(obj.todos).concat(obj.rewards))
|
||||||
|
.sortBy('id') // we don't want to update if they're sort-order is different
|
||||||
|
.transform(function(result, task){
|
||||||
|
result.push(keepAttrs(task));
|
||||||
|
}))
|
||||||
|
.toString(); // for comparing arrays easily
|
||||||
|
}
|
||||||
|
if (comparableData(chal) !== comparableData(req.body)) {
|
||||||
|
User.find({_id: {$in: chal.members}}, function(err, users){
|
||||||
|
console.log('Challenge updated, sync to subscribers');
|
||||||
|
if (err) throw err;
|
||||||
|
_.each(users, function(user){
|
||||||
|
syncChalToUser(chal, user);
|
||||||
|
user.save();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
], function(err, saved){
|
||||||
|
if(err) res.json(500, {err:err});
|
||||||
|
res.json(saved);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
api['delete'] = function(req, res){
|
||||||
|
Challenge.findOneAndRemove({_id:req.params.cid}, function(err, removed){
|
||||||
|
if (err) return res.json(500, {err: err});
|
||||||
|
User.find({_id:{$in: removed.members}}, function(err, users){
|
||||||
|
if (err) throw err;
|
||||||
|
_.each(users, function(user){
|
||||||
|
_.each(user.tasks, function(task){
|
||||||
|
if (task.challenge && task.challenge.id == removed._id) {
|
||||||
|
task.challenge.broken = 'CHALLENGE_DELETED';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
user.save();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs all new tasks, deleted tasks, etc to the user object
|
||||||
|
* @param chal
|
||||||
|
* @param user
|
||||||
|
* @return nothing, user is modified directly. REMEMBER to save the user!
|
||||||
|
*/
|
||||||
|
var syncChalToUser = function(chal, user) {
|
||||||
|
if (!chal || !user) return;
|
||||||
|
|
||||||
|
// Sync tags
|
||||||
|
var tags = user.tags || [];
|
||||||
|
var i = _.findIndex(tags, {id: chal._id})
|
||||||
|
if (~i) {
|
||||||
|
if (tags[i].name !== chal.name) {
|
||||||
|
// update the name - it's been changed since
|
||||||
|
user.tags[i].name = chal.name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.tags.push({
|
||||||
|
id: chal._id,
|
||||||
|
name: chal.name,
|
||||||
|
challenge: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tags = {};
|
||||||
|
tags[chal._id] = true;
|
||||||
|
|
||||||
|
// Sync new tasks and updated tasks
|
||||||
|
_.each(chal.tasks, function(task){
|
||||||
|
var type = task.type;
|
||||||
|
_.defaults(task, {tags: tags, challenge:{}});
|
||||||
|
_.defaults(task.challenge, {id:chal._id});
|
||||||
|
if (user.tasks[task.id]) {
|
||||||
|
_.merge(user.tasks[task.id], keepAttrs(task));
|
||||||
|
} else {
|
||||||
|
user[type+'s'].push(task);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Flag deleted tasks as "broken"
|
||||||
|
_.each(user.tasks, function(task){
|
||||||
|
if (!chal.tasks[task.id]) task.challenge.broken = 'TASK_DELETED';
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
api.join = function(req, res){
|
||||||
|
var user = res.locals.user;
|
||||||
|
var cid = req.params.cid;
|
||||||
|
|
||||||
|
async.waterfall([
|
||||||
|
function(cb){
|
||||||
|
Challenge.findByIdAndUpdate(cid, {$addToSet:{members:user._id}}, cb);
|
||||||
|
},
|
||||||
|
function(challenge, cb){
|
||||||
|
if (!~user.challenges.indexOf(cid))
|
||||||
|
user.challenges.unshift(cid);
|
||||||
|
// Add all challenge's tasks to user's tasks
|
||||||
|
syncChalToUser(challenge, user);
|
||||||
|
user.save(function(err){
|
||||||
|
if (err) return cb(err);
|
||||||
|
cb(null, challenge); // we want the saved challenge in the return results, due to ng-resource
|
||||||
|
});
|
||||||
|
}
|
||||||
|
], function(err, result){
|
||||||
|
if(err) return res.json(500,{err:err});
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlink(user, cid, keep, tid) {
|
||||||
|
switch (keep) {
|
||||||
|
case 'keep':
|
||||||
|
delete user.tasks[tid].challenge;
|
||||||
|
break;
|
||||||
|
case 'remove':
|
||||||
|
user[user.tasks[tid].type+'s'].id(tid).remove();
|
||||||
|
break;
|
||||||
|
case 'keep-all':
|
||||||
|
_.each(user.tasks, function(t){
|
||||||
|
if (t.challenge && t.challenge.id == cid) {
|
||||||
|
delete t.challenge;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'remove-all':
|
||||||
|
_.each(user.tasks, function(t){
|
||||||
|
if (t.challenge && t.challenge.id == cid) {
|
||||||
|
user[t.type+'s'].id(t.id).remove();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.leave = function(req, res){
|
||||||
|
var user = res.locals.user;
|
||||||
|
var cid = req.params.cid;
|
||||||
|
// whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided
|
||||||
|
var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all';
|
||||||
|
|
||||||
|
async.waterfall([
|
||||||
|
function(cb){
|
||||||
|
Challenge.findByIdAndUpdate(cid, {$pull:{members:user._id}}, cb);
|
||||||
|
},
|
||||||
|
function(chal, cb){
|
||||||
|
var i = user.challenges.indexOf(cid)
|
||||||
|
if (~i) user.challenges.splice(i,1);
|
||||||
|
unlink(user, chal._id, keep)
|
||||||
|
user.save(function(err){
|
||||||
|
if (err) return cb(err);
|
||||||
|
cb(null, chal);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
], function(err, result){
|
||||||
|
if(err) return res.json(500,{err:err});
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
api.unlink = function(req, res, next) {
|
||||||
|
// they're scoring the task - commented out, we probably don't need it due to route ordering in api.js
|
||||||
|
//var urlParts = req.originalUrl.split('/');
|
||||||
|
//if (_.contains(['up','down'], urlParts[urlParts.length -1])) return next();
|
||||||
|
|
||||||
|
var user = res.locals.user;
|
||||||
|
var tid = req.params.id;
|
||||||
|
var cid = user.tasks[tid].challenge.id;
|
||||||
|
if (!req.query.keep)
|
||||||
|
return res.json(400, {err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'});
|
||||||
|
unlink(user, cid, req.query.keep, tid);
|
||||||
|
user.save(function(err, saved){
|
||||||
|
if (err) return res.json(500,{err:err});
|
||||||
|
res.send(200);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -16,8 +16,9 @@ var api = module.exports;
|
|||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var usernameFields = 'auth.local.username auth.facebook.displayName auth.facebook.givenName auth.facebook.familyName auth.facebook.name';
|
var itemFields = 'items.armor items.head items.shield items.weapon items.currentPet';
|
||||||
var partyFields = 'profile preferences items stats achievements party backer flags.rest auth.timestamps ' + usernameFields;
|
var partyFields = 'profile preferences stats achievements party backer flags.rest auth.timestamps ' + itemFields;
|
||||||
|
var nameFields = 'profile.name';
|
||||||
|
|
||||||
function removeSelf(group, user){
|
function removeSelf(group, user){
|
||||||
group.members = _.filter(group.members, function(m){return m._id != user._id});
|
group.members = _.filter(group.members, function(m){return m._id != user._id});
|
||||||
@@ -32,83 +33,81 @@ api.getMember = function(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get groups. If req.query.type privided, returned as an array (so ngResource can use). If not, returned as
|
* Fetch groups list. This no longer returns party or tavern, as those can be requested indivdually
|
||||||
* object {guilds, public, party, tavern}. req.query.type can be comma-separated `type=guilds,party`
|
* as /groups/party or /groups/tavern
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @param next
|
|
||||||
*/
|
*/
|
||||||
api.getGroups = function(req, res, next) {
|
api.getGroups = function(req, res) {
|
||||||
var user = res.locals.user;
|
var user = res.locals.user;
|
||||||
|
var groupFields = 'name description memberCount';
|
||||||
|
var sort = '-memberCount';
|
||||||
|
|
||||||
var type = req.query.type && req.query.type.split(',');
|
|
||||||
|
|
||||||
// First get all groups
|
|
||||||
async.parallel({
|
async.parallel({
|
||||||
party: function(cb) {
|
|
||||||
if (type && !~type.indexOf('party')) return cb(null, {});
|
// unecessary given our ui-router setup
|
||||||
Group
|
party: function(cb){
|
||||||
.findOne({type: 'party', members: {'$in': [user._id]}})
|
return cb(null, [{}]);
|
||||||
.populate('members invites', partyFields)
|
|
||||||
.exec(cb);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
guilds: function(cb) {
|
guilds: function(cb) {
|
||||||
if (type && !~type.indexOf('guilds')) return cb(null, []);
|
Group.find({members: {'$in': [user._id]}, type:'guild'})
|
||||||
Group.find({type: 'guild', members: {'$in': [user._id]}}).populate('members invites', usernameFields).exec(cb);
|
.select(groupFields).sort(sort).exec(cb);
|
||||||
// Group.find({type: 'guild', members: {'$in': [user._id]}}, cb);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'public': function(cb) {
|
||||||
|
Group.find({privacy: 'public'})
|
||||||
|
.select(groupFields + ' members')
|
||||||
|
.sort(sort)
|
||||||
|
.exec(function(err, groups){
|
||||||
|
if (err) return cb(err);
|
||||||
|
_.each(groups, function(g){
|
||||||
|
// To save some client-side performance, don't send down the full members arr, just send down temp var _isMember
|
||||||
|
if (~g.members.indexOf(user._id)) g._isMember = true;
|
||||||
|
g.members = undefined;
|
||||||
|
});
|
||||||
|
cb(null, groups);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// unecessary given our ui-router setup
|
||||||
tavern: function(cb) {
|
tavern: function(cb) {
|
||||||
if (type && !~type.indexOf('tavern')) return cb(null, {});
|
return cb(null, [{}]);
|
||||||
Group.findOne({_id: 'habitrpg'}, cb);
|
|
||||||
},
|
|
||||||
"public": function(cb) {
|
|
||||||
if (type && !~type.indexOf('public')) return cb(null, []);
|
|
||||||
Group.find({privacy: 'public'}, {name:1, description:1, members:1}, cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, function(err, results){
|
}, function(err, results){
|
||||||
if (err) return res.json(500, {err: err});
|
if (err) return res.json(500, {err: err});
|
||||||
|
|
||||||
// Remove self from party (see above failing `match` directive in `populate`
|
|
||||||
if (results.party) {
|
|
||||||
removeSelf(results.party, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort public groups by members length (not easily doable in mongoose)
|
|
||||||
results.public = _.sortBy(results.public, function(group){
|
|
||||||
return -group.members.length;
|
|
||||||
});
|
|
||||||
|
|
||||||
// If they're requesting a specific type, let's return it as an array so that $ngResource
|
|
||||||
// can utilize it properly
|
|
||||||
if (type) {
|
|
||||||
results = _.reduce(type, function(m,t){
|
|
||||||
return m.concat(_.isArray(results[t]) ? results[t] : [results[t]]);
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(results);
|
res.json(results);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get group
|
* Get group
|
||||||
|
* TODO: implement requesting fields ?fields=chat,members
|
||||||
*/
|
*/
|
||||||
api.getGroup = function(req, res, next) {
|
api.getGroup = function(req, res) {
|
||||||
var user = res.locals.user;
|
var user = res.locals.user;
|
||||||
var gid = req.params.gid;
|
var gid = req.params.gid;
|
||||||
|
|
||||||
Group.findById(gid).populate('members invites', partyFields).exec(function(err, group){
|
// This will be called for the header, we need extra members' details than usuals
|
||||||
if ( (group.type == 'guild' && group.privacy == 'private') || group.type == 'party') {
|
if (gid == 'party') {
|
||||||
if(!_.find(group.members, {_id: user._id}))
|
Group.findOne({type: 'party', members: {'$in': [user._id]}})
|
||||||
return res.json(401, {err: "You don't have access to this group"});
|
.populate('members invites', partyFields).exec(function(err, group){
|
||||||
}
|
if (err) return res.json(500,{err:err});
|
||||||
// Remove self from party (see above failing `match` directive in `populate`
|
removeSelf(group, user);
|
||||||
if (group.type == 'party') {
|
res.json(group);
|
||||||
removeSelf(group, user);
|
});
|
||||||
}
|
} else {
|
||||||
res.json(group);
|
Group.findById(gid).populate('members invites', nameFields).exec(function(err, group){
|
||||||
|
if ( (group.type == 'guild' && group.privacy == 'private') || group.type == 'party') {
|
||||||
})
|
if(!_.find(group.members, {_id: user._id}))
|
||||||
|
return res.json(401, {err: "You don't have access to this group"});
|
||||||
|
}
|
||||||
|
// Remove self from party (see above failing `match` directive in `populate`
|
||||||
|
if (group.type == 'party') {
|
||||||
|
removeSelf(group, user);
|
||||||
|
}
|
||||||
|
res.json(group);
|
||||||
|
})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -151,7 +150,7 @@ api.updateGroup = function(req, res, next) {
|
|||||||
async.series([
|
async.series([
|
||||||
function(cb){group.save(cb);},
|
function(cb){group.save(cb);},
|
||||||
function(cb){
|
function(cb){
|
||||||
var fields = group.type == 'party' ? partyFields : usernameFields;
|
var fields = group.type == 'party' ? partyFields : nameFields;
|
||||||
Group.findById(group._id).populate('members invites', fields).exec(cb);
|
Group.findById(group._id).populate('members invites', fields).exec(cb);
|
||||||
}
|
}
|
||||||
], function(err, results){
|
], function(err, results){
|
||||||
@@ -178,7 +177,7 @@ api.postChat = function(req, res, next) {
|
|||||||
contributor: user.backer && user.backer.contributor,
|
contributor: user.backer && user.backer.contributor,
|
||||||
npc: user.backer && user.backer.npc,
|
npc: user.backer && user.backer.npc,
|
||||||
text: req.query.message, // FIXME this should be body, but ngResource is funky
|
text: req.query.message, // FIXME this should be body, but ngResource is funky
|
||||||
user: helpers.username(user.auth, user.profile.name),
|
user: user.profile.name,
|
||||||
timestamp: +(new Date)
|
timestamp: +(new Date)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
/* @see ./routes.coffee for routing*/
|
/* @see ./routes.coffee for routing*/
|
||||||
|
|
||||||
// fixme remove this junk, was coffeescript compiled (probably for IE8 compat)
|
|
||||||
var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
|
||||||
|
|
||||||
var url = require('url');
|
var url = require('url');
|
||||||
var ipn = require('paypal-ipn');
|
var ipn = require('paypal-ipn');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
@@ -16,6 +13,7 @@ var check = validator.check;
|
|||||||
var sanitize = validator.sanitize;
|
var sanitize = validator.sanitize;
|
||||||
var User = require('./../models/user').model;
|
var User = require('./../models/user').model;
|
||||||
var Group = require('./../models/group').model;
|
var Group = require('./../models/group').model;
|
||||||
|
var Challenge = require('./../models/challenge').model;
|
||||||
var api = module.exports;
|
var api = module.exports;
|
||||||
|
|
||||||
// FIXME put this in a proper location
|
// FIXME put this in a proper location
|
||||||
@@ -55,155 +53,86 @@ api.marketBuy = function(req, res, next){
|
|||||||
---------------
|
---------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
// FIXME put this in helpers, so mobile & web can us it too
|
|
||||||
// FIXME actually, move to mongoose
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function taskSanitizeAndDefaults(task) {
|
|
||||||
var _ref;
|
|
||||||
if (task.id == null) {
|
|
||||||
task.id = helpers.uuid();
|
|
||||||
}
|
|
||||||
task.value = ~~task.value;
|
|
||||||
if (task.type == null) {
|
|
||||||
task.type = 'habit';
|
|
||||||
}
|
|
||||||
if (_.isString(task.text)) {
|
|
||||||
task.text = sanitize(task.text).xss();
|
|
||||||
}
|
|
||||||
if (_.isString(task.text)) {
|
|
||||||
task.notes = sanitize(task.notes).xss();
|
|
||||||
}
|
|
||||||
if (task.type === 'habit') {
|
|
||||||
if (!_.isBoolean(task.up)) {
|
|
||||||
task.up = true;
|
|
||||||
}
|
|
||||||
if (!_.isBoolean(task.down)) {
|
|
||||||
task.down = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((_ref = task.type) === 'daily' || _ref === 'todo') {
|
|
||||||
if (!_.isBoolean(task.completed)) {
|
|
||||||
task.completed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (task.type === 'daily') {
|
|
||||||
if (task.repeat == null) {
|
|
||||||
task.repeat = {
|
|
||||||
m: true,
|
|
||||||
t: true,
|
|
||||||
w: true,
|
|
||||||
th: true,
|
|
||||||
f: true,
|
|
||||||
s: true,
|
|
||||||
su: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return task;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Validate task
|
Validate task
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.verifyTaskExists = function(req, res, next) {
|
api.verifyTaskExists = function(req, res, next) {
|
||||||
/* If we're updating, get the task from the user*/
|
// If we're updating, get the task from the user
|
||||||
|
var task = res.locals.user.tasks[req.params.id];
|
||||||
var task;
|
if (_.isEmpty(task)) return res.json(400, {err: "No task found."});
|
||||||
task = res.locals.user.tasks[req.params.id];
|
|
||||||
if (_.isEmpty(task)) {
|
|
||||||
return res.json(400, {
|
|
||||||
err: "No task found."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
res.locals.task = task;
|
res.locals.task = task;
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
|
|
||||||
function addTask(user, task) {
|
|
||||||
taskSanitizeAndDefaults(task);
|
|
||||||
user.tasks[task.id] = task;
|
|
||||||
user["" + task.type + "Ids"].unshift(task.id);
|
|
||||||
return task;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Override current user.task with incoming values, then sanitize all values*/
|
|
||||||
|
|
||||||
|
|
||||||
function updateTask(user, id, incomingTask) {
|
|
||||||
return user.tasks[id] = taskSanitizeAndDefaults(_.defaults(incomingTask, user.tasks[id]));
|
|
||||||
};
|
|
||||||
|
|
||||||
function deleteTask(user, task) {
|
function deleteTask(user, task) {
|
||||||
var i, ids;
|
user[task.type+'s'].id(task.id).remove();
|
||||||
delete user.tasks[task.id];
|
|
||||||
if ((ids = user["" + task.type + "Ids"]) && ~(i = ids.indexOf(task.id))) {
|
|
||||||
return ids.splice(i, 1);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function addTask(user, task) {
|
||||||
|
var type = task.type || 'habit'
|
||||||
|
user[type+'s'].unshift(task);
|
||||||
|
// FIXME will likely have to use taskSchema instead, so we can populate the defaults, add the _id, and return the added task
|
||||||
|
return user[task.type+'s'][0];
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
API Routes
|
API Routes
|
||||||
---------------
|
---------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var syncScoreToChallenge = function(task, delta){
|
||||||
|
if (!task.challenge || !task.challenge.id) return;
|
||||||
|
Challenge.findById(task.challenge.id, function(err, chal){
|
||||||
|
if (err) throw err;
|
||||||
|
var t = chal.tasks[task.id]
|
||||||
|
t.value += delta;
|
||||||
|
t.history.push({value: t.value, date: +new Date});
|
||||||
|
chal.save();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This is called form deprecated.coffee's score function, and the req.headers are setup properly to handle the login
|
This is called form deprecated.coffee's score function, and the req.headers are setup properly to handle the login
|
||||||
Export it also so we can call it from deprecated.coffee
|
Export it also so we can call it from deprecated.coffee
|
||||||
*/
|
*/
|
||||||
api.scoreTask = function(req, res, next) {
|
api.scoreTask = function(req, res, next) {
|
||||||
|
var id = req.params.id,
|
||||||
// FIXME this is all uglified from coffeescript compile, clean this up
|
direction = req.params.direction,
|
||||||
|
user = res.locals.user,
|
||||||
var delta, direction, existing, id, task, user, _ref, _ref1, _ref2, _ref3, _ref4;
|
task;
|
||||||
_ref = req.params, id = _ref.id, direction = _ref.direction;
|
|
||||||
|
|
||||||
// Send error responses for improper API call
|
// Send error responses for improper API call
|
||||||
if (!id) {
|
if (!id) return res.json(500, {err: ':id required'});
|
||||||
return res.json(500, {
|
|
||||||
err: ':id required'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (direction !== 'up' && direction !== 'down') {
|
if (direction !== 'up' && direction !== 'down') {
|
||||||
return res.json(500, {
|
if (direction == 'unlink') return next();
|
||||||
err: ":direction must be 'up' or 'down'"
|
return res.json(500, {err: ":direction must be 'up' or 'down'"});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
user = res.locals.user;
|
// If exists already, score it
|
||||||
/* If exists already, score it*/
|
var existing;
|
||||||
|
if (existing = user.tasks[id]) {
|
||||||
if ((existing = user.tasks[id])) {
|
// Set completed if type is daily or todo and task exists
|
||||||
/* Set completed if type is daily or todo and task exists*/
|
if (existing.type === 'daily' || existing.type === 'todo') {
|
||||||
|
|
||||||
if ((_ref1 = existing.type) === 'daily' || _ref1 === 'todo') {
|
|
||||||
existing.completed = direction === 'up';
|
existing.completed = direction === 'up';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it*/
|
// If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it
|
||||||
|
|
||||||
task = {
|
task = {
|
||||||
id: id,
|
id: id,
|
||||||
value: 0,
|
value: 0,
|
||||||
type: ((_ref2 = req.body) != null ? _ref2.type : void 0) || 'habit',
|
type: req.body.type || 'habit',
|
||||||
text: ((_ref3 = req.body) != null ? _ref3.title : void 0) || id,
|
text: req.body.title || id,
|
||||||
notes: "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task."
|
notes: "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task."
|
||||||
};
|
};
|
||||||
if (task.type === 'habit') {
|
if (task.type === 'habit') {
|
||||||
task.up = task.down = true;
|
task.up = task.down = true;
|
||||||
}
|
}
|
||||||
if ((_ref4 = task.type) === 'daily' || _ref4 === 'todo') {
|
if (task.type === 'daily' || task.type === 'todo') {
|
||||||
task.completed = direction === 'up';
|
task.completed = direction === 'up';
|
||||||
}
|
}
|
||||||
addTask(user, task);
|
addTask(user, task);
|
||||||
}
|
}
|
||||||
task = user.tasks[id];
|
task = user.tasks[id];
|
||||||
delta = algos.score(user, task, direction);
|
var delta = algos.score(user, task, direction);
|
||||||
//user.markModified('flags');
|
//user.markModified('flags');
|
||||||
user.save(function(err, saved) {
|
user.save(function(err, saved) {
|
||||||
if (err) return res.json(500, {err: err});
|
if (err) return res.json(500, {err: err});
|
||||||
@@ -211,44 +140,34 @@ api.scoreTask = function(req, res, next) {
|
|||||||
delta: delta
|
delta: delta
|
||||||
}, saved.toJSON().stats));
|
}, saved.toJSON().stats));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// if it's a challenge task, sync the score
|
||||||
|
syncScoreToChallenge(task, delta);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Get all tasks
|
* Get all tasks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.getTasks = function(req, res, next) {
|
api.getTasks = function(req, res, next) {
|
||||||
var tasks, types, _ref;
|
if (req.query.type) {
|
||||||
types = (_ref = req.query.type) === 'habit' || _ref === 'todo' || _ref === 'daily' || _ref === 'reward' ? [req.query.type] : ['habit', 'todo', 'daily', 'reward'];
|
return res.json(user[req.query.type+'s']);
|
||||||
tasks = _.toArray(_.filter(res.locals.user.tasks, function(t) {
|
} else {
|
||||||
var _ref1;
|
return res.json(_.toArray(user.tasks));
|
||||||
return _ref1 = t.type, __indexOf.call(types, _ref1) >= 0;
|
}
|
||||||
}));
|
|
||||||
return res.json(200, tasks);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Get Task
|
* Get Task
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.getTask = function(req, res, next) {
|
api.getTask = function(req, res, next) {
|
||||||
var task;
|
var task = res.locals.user.tasks[req.params.id];
|
||||||
task = res.locals.user.tasks[req.params.id];
|
if (_.isEmpty(task)) return res.json(400, {err: "No task found."});
|
||||||
if (_.isEmpty(task)) {
|
|
||||||
return res.json(400, {
|
|
||||||
err: "No task found."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(200, task);
|
return res.json(200, task);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Delete Task
|
* Delete Task
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.deleteTask = function(req, res, next) {
|
api.deleteTask = function(req, res, next) {
|
||||||
deleteTask(res.locals.user, res.locals.task);
|
deleteTask(res.locals.user, res.locals.task);
|
||||||
res.locals.user.save(function(err) {
|
res.locals.user.save(function(err) {
|
||||||
@@ -263,113 +182,69 @@ api.deleteTask = function(req, res, next) {
|
|||||||
|
|
||||||
|
|
||||||
api.updateTask = function(req, res, next) {
|
api.updateTask = function(req, res, next) {
|
||||||
var id, user;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
var task = user.tasks[req.params.id];
|
||||||
id = req.params.id;
|
user[task.type+'s'][_.findIndex(user[task.type+'s'],{id:task.id})] = req.body;
|
||||||
updateTask(user, id, req.body);
|
user.save(function(err, saved) {
|
||||||
return user.save(function(err, saved) {
|
if (err) return res.json(500, {err: err})
|
||||||
if (err) {
|
return res.json(200, saved.tasks[id]);
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(200, _.findWhere(saved.toJSON().tasks, {
|
|
||||||
id: id
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Update tasks (plural). This will update, add new, delete, etc all at once.
|
* Update tasks (plural). This will update, add new, delete, etc all at once.
|
||||||
Should we keep this?
|
* TODO Should we keep this?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.updateTasks = function(req, res, next) {
|
api.updateTasks = function(req, res, next) {
|
||||||
var tasks, user;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
var tasks = req.body;
|
||||||
tasks = req.body;
|
|
||||||
_.each(tasks, function(task, idx) {
|
_.each(tasks, function(task, idx) {
|
||||||
if (task.id) {
|
if (task.id) {
|
||||||
/*delete*/
|
// delete
|
||||||
|
|
||||||
if (task.del) {
|
if (task.del) {
|
||||||
deleteTask(user, task);
|
deleteTask(user, task);
|
||||||
task = {
|
task = {deleted: true};
|
||||||
deleted: true
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
/* Update*/
|
// Update
|
||||||
|
// updateTask(user, task.id, task); //FIXME
|
||||||
updateTask(user, task.id, task);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Create*/
|
// Create
|
||||||
|
|
||||||
task = addTask(user, task);
|
task = addTask(user, task);
|
||||||
}
|
}
|
||||||
return tasks[idx] = task;
|
tasks[idx] = task;
|
||||||
});
|
});
|
||||||
return user.save(function(err, saved) {
|
user.save(function(err, saved) {
|
||||||
if (err) {
|
if (err) return res.json(500, {err: err});
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(201, tasks);
|
return res.json(201, tasks);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
api.createTask = function(req, res, next) {
|
api.createTask = function(req, res, next) {
|
||||||
var task, user;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
var task = addTask(user, req.body);
|
||||||
task = addTask(user, req.body);
|
user.save(function(err, saved) {
|
||||||
return user.save(function(err) {
|
if (err) return res.json(500, {err: err});
|
||||||
if (err) {
|
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(201, task);
|
return res.json(201, task);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
api.sortTask = function(req, res, next) {
|
api.sortTask = function(req, res, next) {
|
||||||
var from, id, path, to, type, user, _ref;
|
var id = req.params.id;
|
||||||
id = req.params.id;
|
var to = req.body.to, from = req.body.from, type = req.body.type;
|
||||||
_ref = req.body, to = _ref.to, from = _ref.from, type = _ref.type;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
user[type+'s'].splice(to, 0, user[type+'s'].splice(from, 1)[0]);
|
||||||
path = "" + type + "Ids";
|
user.save(function(err, saved) {
|
||||||
user[path].splice(to, 0, user[path].splice(from, 1)[0]);
|
if (err) return res.json(500, {err: err});
|
||||||
return user.save(function(err, saved) {
|
return res.json(200, saved.toJSON()[type+'s']);
|
||||||
if (err) {
|
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(200, saved.toJSON()[path]);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
api.clearCompleted = function(req, res, next) {
|
api.clearCompleted = function(req, res, next) {
|
||||||
var completedIds, todoIds, user;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
user.todos = _.where(user.todos, {completed: false});
|
||||||
completedIds = _.pluck(_.where(user.tasks, {
|
|
||||||
type: 'todo',
|
|
||||||
completed: true
|
|
||||||
}), 'id');
|
|
||||||
todoIds = user.todoIds;
|
|
||||||
_.each(completedIds, function(id) {
|
|
||||||
delete user.tasks[id];
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
user.todoIds = _.difference(todoIds, completedIds);
|
|
||||||
return user.save(function(err, saved) {
|
return user.save(function(err, saved) {
|
||||||
if (err) {
|
if (err) return res.json(500, {err: err});
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(saved);
|
return res.json(saved);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -379,31 +254,21 @@ api.clearCompleted = function(req, res, next) {
|
|||||||
Items
|
Items
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.buy = function(req, res, next) {
|
api.buy = function(req, res, next) {
|
||||||
var hasEnough, type, user;
|
var hasEnough, type, user;
|
||||||
user = res.locals.user;
|
user = res.locals.user;
|
||||||
type = req.params.type;
|
type = req.params.type;
|
||||||
if (type !== 'weapon' && type !== 'armor' && type !== 'head' && type !== 'shield' && type !== 'potion') {
|
if (type !== 'weapon' && type !== 'armor' && type !== 'head' && type !== 'shield' && type !== 'potion') {
|
||||||
return res.json(400, {
|
return res.json(400, {err: ":type must be in one of: 'weapon', 'armor', 'head', 'shield', 'potion'"});
|
||||||
err: ":type must be in one of: 'weapon', 'armor', 'head', 'shield', 'potion'"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
hasEnough = items.buyItem(user, type);
|
hasEnough = items.buyItem(user, type);
|
||||||
if (hasEnough) {
|
if (hasEnough) {
|
||||||
return user.save(function(err, saved) {
|
return user.save(function(err, saved) {
|
||||||
if (err) {
|
if (err) return res.json(500, {err: err});
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(200, saved.toJSON().items);
|
return res.json(200, saved.toJSON().items);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return res.json(200, {
|
return res.json(200, {err: "Not enough GP"});
|
||||||
err: "Not enough GP"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -413,11 +278,9 @@ api.buy = function(req, res, next) {
|
|||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Get User
|
* Get User
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.getUser = function(req, res, next) {
|
api.getUser = function(req, res, next) {
|
||||||
var user = res.locals.user.toJSON();
|
var user = res.locals.user.toJSON();
|
||||||
user.stats.toNextLevel = algos.tnl(user.stats.lvl);
|
user.stats.toNextLevel = algos.tnl(user.stats.lvl);
|
||||||
@@ -430,12 +293,10 @@ api.getUser = function(req, res, next) {
|
|||||||
return res.json(200, user);
|
return res.json(200, user);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Update user
|
* Update user
|
||||||
FIXME add documentation here
|
* FIXME add documentation here
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.updateUser = function(req, res, next) {
|
api.updateUser = function(req, res, next) {
|
||||||
var acceptableAttrs, errors, user;
|
var acceptableAttrs, errors, user;
|
||||||
user = res.locals.user;
|
user = res.locals.user;
|
||||||
@@ -483,8 +344,7 @@ api.updateUser = function(req, res, next) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
api.cron = function(req, res, next) {
|
api.cron = function(req, res, next) {
|
||||||
var user;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
|
||||||
algos.cron(user);
|
algos.cron(user);
|
||||||
if (user.isModified()) {
|
if (user.isModified()) {
|
||||||
res.locals.wasModified = true;
|
res.locals.wasModified = true;
|
||||||
@@ -494,52 +354,36 @@ api.cron = function(req, res, next) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
api.revive = function(req, res, next) {
|
api.revive = function(req, res, next) {
|
||||||
var user;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
|
||||||
algos.revive(user);
|
algos.revive(user);
|
||||||
return user.save(function(err, saved) {
|
user.save(function(err, saved) {
|
||||||
if (err) {
|
if (err) return res.json(500, {err: err});
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(200, saved);
|
return res.json(200, saved);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
api.reroll = function(req, res, next) {
|
api.reroll = function(req, res, next) {
|
||||||
var user;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
if (user.balance < 1) return res.json(401, {err: "Not enough tokens."});
|
||||||
if (user.balance < 1) {
|
|
||||||
return res.json(401, {
|
|
||||||
err: "Not enough tokens."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
user.balance -= 1;
|
user.balance -= 1;
|
||||||
_.each(user.tasks, function(task) {
|
_.each(['habits','dailys','todos'], function(type){
|
||||||
if (task.type !== 'reward') {
|
_.each([user[type+'s']], function(task){
|
||||||
user.tasks[task.id].value = 0;
|
task.value = 0;
|
||||||
}
|
})
|
||||||
return true;
|
})
|
||||||
});
|
|
||||||
user.stats.hp = 50;
|
user.stats.hp = 50;
|
||||||
return user.save(function(err, saved) {
|
user.save(function(err, saved) {
|
||||||
if (err) {
|
if (err) return res.json(500, {err: err});
|
||||||
return res.json(500, {
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.json(200, saved);
|
return res.json(200, saved);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
api.reset = function(req, res){
|
api.reset = function(req, res){
|
||||||
var user = res.locals.user;
|
var user = res.locals.user;
|
||||||
user.tasks = {};
|
user.habits = [];
|
||||||
|
user.dailys = [];
|
||||||
_.each(['habit', 'daily', 'todo', 'reward'], function(type) {
|
user.todos = [];
|
||||||
user[type + "Ids"] = [];
|
user.rewards = [];
|
||||||
});
|
|
||||||
|
|
||||||
user.stats.hp = 50;
|
user.stats.hp = 50;
|
||||||
user.stats.lvl = 1;
|
user.stats.lvl = 1;
|
||||||
@@ -675,9 +519,11 @@ api.deleteTag = function(req, res){
|
|||||||
delete user.filters[tag.id];
|
delete user.filters[tag.id];
|
||||||
user.tags.splice(i,1);
|
user.tags.splice(i,1);
|
||||||
// remove tag from all tasks
|
// remove tag from all tasks
|
||||||
_.each(user.tasks, function(task) {
|
_.each(['habits','dailys','todos','rewards'], function(type){
|
||||||
delete user.tasks[task.id].tags[tag.id];
|
_.each(user[type], function(task){
|
||||||
});
|
delete task.tags[tag.id];
|
||||||
|
})
|
||||||
|
})
|
||||||
user.save(function(err,saved){
|
user.save(function(err,saved){
|
||||||
if (err) return res.json(500, {err: err});
|
if (err) return res.json(500, {err: err});
|
||||||
// Need to use this until we found a way to update the ui for tasks when a tag is deleted
|
// Need to use this until we found a way to update the ui for tasks when a tag is deleted
|
||||||
@@ -695,22 +541,16 @@ api.deleteTag = function(req, res){
|
|||||||
Run a bunch of updates all at once
|
Run a bunch of updates all at once
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
api.batchUpdate = function(req, res, next) {
|
api.batchUpdate = function(req, res, next) {
|
||||||
var actions, oldJson, oldSend, performAction, user, _ref;
|
var user = res.locals.user;
|
||||||
user = res.locals.user;
|
var oldSend = res.send;
|
||||||
oldSend = res.send;
|
var oldJson = res.json;
|
||||||
oldJson = res.json;
|
var performAction = function(action, cb) {
|
||||||
performAction = function(action, cb) {
|
|
||||||
/*
|
|
||||||
# TODO come up with a more consistent approach here. like:
|
|
||||||
# req.body=action.data; delete action.data; _.defaults(req.params, action)
|
|
||||||
# Would require changing action.dir on mobile app
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ref;
|
// TODO come up with a more consistent approach here. like:
|
||||||
req.params.id = (_ref = action.data) != null ? _ref.id : void 0;
|
// req.body=action.data; delete action.data; _.defaults(req.params, action)
|
||||||
|
// Would require changing action.dir on mobile app
|
||||||
|
req.params.id = action.data && action.data.id;
|
||||||
req.params.direction = action.dir;
|
req.params.direction = action.dir;
|
||||||
req.params.type = action.type;
|
req.params.type = action.type;
|
||||||
req.body = action.data;
|
req.body = action.data;
|
||||||
@@ -764,27 +604,22 @@ api.batchUpdate = function(req, res, next) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/* Setup the array of functions we're going to call in parallel with async*/
|
|
||||||
|
|
||||||
actions = _.transform((_ref = req.body) != null ? _ref : [], function(result, action) {
|
// Setup the array of functions we're going to call in parallel with async
|
||||||
|
var actions = _.transform(req.body || [], function(result, action) {
|
||||||
if (!_.isEmpty(action)) {
|
if (!_.isEmpty(action)) {
|
||||||
return result.push(function(cb) {
|
result.push(function(cb) {
|
||||||
return performAction(action, cb);
|
performAction(action, cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
/* call all the operations, then return the user object to the requester*/
|
|
||||||
|
|
||||||
return async.series(actions, function(err) {
|
// call all the operations, then return the user object to the requester
|
||||||
var response;
|
async.series(actions, function(err) {
|
||||||
res.json = oldJson;
|
res.json = oldJson;
|
||||||
res.send = oldSend;
|
res.send = oldSend;
|
||||||
if (err) {
|
if (err) return res.json(500, {err: err});
|
||||||
return res.json(500, {
|
var response = user.toJSON();
|
||||||
err: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
response = user.toJSON();
|
|
||||||
response.wasModified = res.locals.wasModified;
|
response.wasModified = res.locals.wasModified;
|
||||||
if (response._tmp && response._tmp.drop) response.wasModified = true;
|
if (response._tmp && response._tmp.drop) response.wasModified = true;
|
||||||
|
|
||||||
@@ -794,7 +629,5 @@ api.batchUpdate = function(req, res, next) {
|
|||||||
}else{
|
}else{
|
||||||
res.json(200, {_v: response._v});
|
res.json(200, {_v: response._v});
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
34
src/models/challenge.js
Normal file
34
src/models/challenge.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
var mongoose = require("mongoose");
|
||||||
|
var Schema = mongoose.Schema;
|
||||||
|
var helpers = require('habitrpg-shared/script/helpers');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var TaskSchema = require('./task').schema;
|
||||||
|
|
||||||
|
var ChallengeSchema = new Schema({
|
||||||
|
_id: {type: String, 'default': helpers.uuid},
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
habits: [TaskSchema],
|
||||||
|
dailys: [TaskSchema],
|
||||||
|
todos: [TaskSchema],
|
||||||
|
rewards: [TaskSchema],
|
||||||
|
leader: {type: String, ref: 'User'},
|
||||||
|
group: {type: String, ref: 'Group'},
|
||||||
|
// FIXME remove below, we don't need it since every time we load a challenge, we'll load it with the group ref. we don't need to look up challenges by type
|
||||||
|
//type: group.type, //type: {type: String,"enum": ['guild', 'party']},
|
||||||
|
//id: group._id
|
||||||
|
//},
|
||||||
|
timestamp: {type: Date, 'default': Date.now},
|
||||||
|
members: [{type: String, ref: 'User'}]
|
||||||
|
}, {
|
||||||
|
minimize: 'false'
|
||||||
|
});
|
||||||
|
|
||||||
|
ChallengeSchema.virtual('tasks').get(function () {
|
||||||
|
var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards);
|
||||||
|
var tasks = _.object(_.pluck(tasks,'id'), tasks);
|
||||||
|
return tasks;
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.schema = ChallengeSchema;
|
||||||
|
module.exports.model = mongoose.model("Challenge", ChallengeSchema);
|
||||||
@@ -7,37 +7,14 @@ var GroupSchema = new Schema({
|
|||||||
_id: {type: String, 'default': helpers.uuid},
|
_id: {type: String, 'default': helpers.uuid},
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
leader: {
|
leader: {type: String, ref: 'User'},
|
||||||
type: String,
|
members: [{type: String, ref: 'User'}],
|
||||||
ref: 'User'
|
invites: [{type: String, ref: 'User'}],
|
||||||
},
|
type: {type: String, "enum": ['guild', 'party']},
|
||||||
members: [
|
privacy: {type: String, "enum": ['private', 'public']},
|
||||||
{
|
_v: {Number: Number,'default': 0},
|
||||||
type: String,
|
|
||||||
ref: 'User'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
invites: [
|
|
||||||
{
|
|
||||||
type: String,
|
|
||||||
ref: 'User'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
"enum": ['guild', 'party']
|
|
||||||
},
|
|
||||||
privacy: {
|
|
||||||
type: String,
|
|
||||||
"enum": ['private', 'public']
|
|
||||||
},
|
|
||||||
_v: {
|
|
||||||
Number: Number,
|
|
||||||
'default': 0
|
|
||||||
},
|
|
||||||
websites: Array,
|
websites: Array,
|
||||||
chat: Array,
|
chat: Array,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
# [{
|
# [{
|
||||||
# timestamp: Date
|
# timestamp: Date
|
||||||
@@ -49,15 +26,17 @@ var GroupSchema = new Schema({
|
|||||||
# }]
|
# }]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
memberCount: {type: Number, 'default': 0},
|
||||||
|
challengeCount: {type: Number, 'default': 0},
|
||||||
balance: Number,
|
balance: Number,
|
||||||
logo: String,
|
logo: String,
|
||||||
leaderMessage: String
|
leaderMessage: String,
|
||||||
|
challenges: [{type:'String', ref:'Challenge'}]
|
||||||
}, {
|
}, {
|
||||||
strict: 'throw',
|
strict: 'throw',
|
||||||
minimize: false // So empty objects are returned
|
minimize: false // So empty objects are returned
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derby duplicated stuff. This is a temporary solution, once we're completely off derby we'll run an mongo migration
|
* Derby duplicated stuff. This is a temporary solution, once we're completely off derby we'll run an mongo migration
|
||||||
* to remove duplicates, then take these fucntions out
|
* to remove duplicates, then take these fucntions out
|
||||||
@@ -81,12 +60,15 @@ function removeDuplicates(doc){
|
|||||||
|
|
||||||
GroupSchema.pre('save', function(next){
|
GroupSchema.pre('save', function(next){
|
||||||
removeDuplicates(this);
|
removeDuplicates(this);
|
||||||
|
this.memberCount = _.size(this.members);
|
||||||
|
this.challengeCount = _.size(this.challenges);
|
||||||
next();
|
next();
|
||||||
})
|
})
|
||||||
|
|
||||||
GroupSchema.methods.toJSON = function(){
|
GroupSchema.methods.toJSON = function(){
|
||||||
var doc = this.toObject();
|
var doc = this.toObject();
|
||||||
removeDuplicates(doc);
|
removeDuplicates(doc);
|
||||||
|
doc._isMember = this._isMember;
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
48
src/models/task.js
Normal file
48
src/models/task.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// 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');
|
||||||
|
|
||||||
|
// Task Schema
|
||||||
|
// -----------
|
||||||
|
|
||||||
|
var TaskSchema = new Schema({
|
||||||
|
history: [{date:Date, value:Number}],
|
||||||
|
_id:{type: String,'default': helpers.uuid},
|
||||||
|
text: String,
|
||||||
|
notes: {type: String, 'default': ''},
|
||||||
|
tags: {type: Schema.Types.Mixed, 'default': {}}, //{ "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true },
|
||||||
|
type: {type:String, 'default': 'habit'}, // habit, daily
|
||||||
|
up: {type: Boolean, 'default': true},
|
||||||
|
down: {type: Boolean, 'default': true},
|
||||||
|
value: {type: Number, 'default': 0},
|
||||||
|
completed: {type: Boolean, 'default': false},
|
||||||
|
priority: {type: String, 'default': '!'}, //'!!' // FIXME this should be a number or something
|
||||||
|
repeat: {type: Schema.Types.Mixed, 'default': {m:1, t:1, w:1, th:1, f:1, s:1, su:1} },
|
||||||
|
streak: {type: Number, 'default': 0},
|
||||||
|
challenge: {
|
||||||
|
id: {type: 'String', ref:'Challenge'},
|
||||||
|
broken: String // CHALLENGE_DELETED, TASK_DELETED, UNSUBSCRIBED, etc
|
||||||
|
// group: {type: 'Strign', ref: 'Group'} // if we restore this, rename `id` above to `challenge`
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
minimize: 'false'
|
||||||
|
});
|
||||||
|
|
||||||
|
TaskSchema.methods.toJSON = function() {
|
||||||
|
var doc = this.toObject();
|
||||||
|
doc.id = doc._id;
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
TaskSchema.virtual('id').get(function(){
|
||||||
|
return this._id;
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports.schema = TaskSchema;
|
||||||
|
module.exports.model = mongoose.model("Task", TaskSchema);
|
||||||
@@ -8,6 +8,7 @@ var mongoose = require("mongoose");
|
|||||||
var Schema = mongoose.Schema;
|
var Schema = mongoose.Schema;
|
||||||
var helpers = require('habitrpg-shared/script/helpers');
|
var helpers = require('habitrpg-shared/script/helpers');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var TaskSchema = require('./task').schema;
|
||||||
|
|
||||||
// User Schema
|
// User Schema
|
||||||
// -----------
|
// -----------
|
||||||
@@ -63,10 +64,6 @@ var UserSchema = new Schema({
|
|||||||
},
|
},
|
||||||
|
|
||||||
balance: Number,
|
balance: Number,
|
||||||
habitIds: Array,
|
|
||||||
dailyIds: Array,
|
|
||||||
todoIds: Array,
|
|
||||||
rewardIds: Array,
|
|
||||||
filters: {type: Schema.Types.Mixed, 'default': {}},
|
filters: {type: Schema.Types.Mixed, 'default': {}},
|
||||||
|
|
||||||
purchased: {
|
purchased: {
|
||||||
@@ -204,79 +201,56 @@ var UserSchema = new Schema({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// ### Tasks Definition
|
challenges: [{type: 'String', ref:'Challenge'}],
|
||||||
// We can't define `tasks` until we move off Derby, since Derby requires dictionary of objects. When we're off, migrate
|
|
||||||
// to array of subdocs
|
|
||||||
|
|
||||||
tasks: Schema.Types.Mixed
|
habits: [TaskSchema],
|
||||||
/*
|
dailys: [TaskSchema],
|
||||||
# history: {date, value}
|
todos: [TaskSchema],
|
||||||
# id
|
rewards: [TaskSchema],
|
||||||
# notes
|
|
||||||
# tags { "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true },
|
|
||||||
# text
|
|
||||||
# type
|
|
||||||
# up
|
|
||||||
# down
|
|
||||||
# value
|
|
||||||
# completed
|
|
||||||
# priority: '!!'
|
|
||||||
# repeat {m: true, t: true}
|
|
||||||
# streak
|
|
||||||
*/
|
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
strict: true,
|
strict: true,
|
||||||
minimize: false // So empty objects are returned
|
minimize: false // So empty objects are returned
|
||||||
});
|
});
|
||||||
|
|
||||||
// Legacy Derby Function?
|
|
||||||
// ----------------------
|
|
||||||
// Derby requires a strange storage format for somethign called "refLists". Here we hook into loading the data, so we
|
|
||||||
// can provide a more "expected" storage format for our various helper methods. Since the attributes are passed by reference,
|
|
||||||
// the underlying data will be modified too - so when we save back to the database, it saves it in the way Derby likes.
|
|
||||||
// This will go away after the rewrite is complete
|
|
||||||
|
|
||||||
function transformTaskLists(doc) {
|
|
||||||
_.each(['habit', 'daily', 'todo', 'reward'], function(type) {
|
|
||||||
// we use _.transform instead of a simple _.where in order to maintain sort-order
|
|
||||||
doc[type + "s"] = _.reduce(doc[type + "Ids"], function(m, tid) {
|
|
||||||
if (!doc.tasks[tid]) return m; // FIXME tmp hotfix, people still have null tasks?
|
|
||||||
if (!doc.tasks[tid].tags) doc.tasks[tid].tags = {}; // FIXME remove this when we switch tasks to subdocs and can define tags default in schema
|
|
||||||
m.push(doc.tasks[tid]);
|
|
||||||
return m;
|
|
||||||
}, []);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSchema.post('init', function(doc) {
|
|
||||||
transformTaskLists(doc);
|
|
||||||
});
|
|
||||||
|
|
||||||
UserSchema.methods.toJSON = function() {
|
UserSchema.methods.toJSON = function() {
|
||||||
var doc = this.toObject();
|
var doc = this.toObject();
|
||||||
doc.id = doc._id;
|
doc.id = doc._id;
|
||||||
transformTaskLists(doc); // we need to also transform for our server-side routes
|
|
||||||
|
|
||||||
// FIXME? Is this a reference to `doc.filters` or just disabled code? Remove?
|
// FIXME? Is this a reference to `doc.filters` or just disabled code? Remove?
|
||||||
/*
|
|
||||||
// Remove some unecessary data as far as client consumers are concerned
|
|
||||||
//_.each(['habit', 'daily', 'todo', 'reward'], function(type) {
|
|
||||||
// delete doc["#{type}Ids"]
|
|
||||||
//});
|
|
||||||
//delete doc.tasks
|
|
||||||
*/
|
|
||||||
doc.filters = {};
|
doc.filters = {};
|
||||||
doc._tmp = this._tmp; // be sure to send down drop notifs
|
doc._tmp = this._tmp; // be sure to send down drop notifs
|
||||||
|
|
||||||
|
// TODO why isnt' this happening automatically given the TaskSchema.methods.toJSON above?
|
||||||
|
_.each(['habits','dailys','todos','rewards'], function(type){
|
||||||
|
_.each(doc[type],function(task){
|
||||||
|
task.id = task._id;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UserSchema.virtual('tasks').get(function () {
|
||||||
|
var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards);
|
||||||
|
var tasks = _.object(_.pluck(tasks,'id'), tasks);
|
||||||
|
return tasks;
|
||||||
|
});
|
||||||
|
|
||||||
// FIXME - since we're using special @post('init') above, we need to flag when the original path was modified.
|
// FIXME - since we're using special @post('init') above, we need to flag when the original path was modified.
|
||||||
// Custom setter/getter virtuals?
|
// Custom setter/getter virtuals?
|
||||||
|
|
||||||
UserSchema.pre('save', function(next) {
|
UserSchema.pre('save', function(next) {
|
||||||
this.markModified('tasks');
|
//this.markModified('tasks');
|
||||||
|
|
||||||
|
if (!this.profile.name) {
|
||||||
|
var fb = this.auth.facebook;
|
||||||
|
this.profile.name =
|
||||||
|
(this.auth.local && this.auth.local.username) ||
|
||||||
|
(fb && (fb.displayName || fb.name || fb.username || (fb.first_name && fb.first_name + ' ' + fb.last_name))) ||
|
||||||
|
'Anonymous';
|
||||||
|
}
|
||||||
|
|
||||||
//our own version incrementer
|
//our own version incrementer
|
||||||
this._v++;
|
this._v++;
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ var router = new express.Router();
|
|||||||
var user = require('../controllers/user');
|
var user = require('../controllers/user');
|
||||||
var groups = require('../controllers/groups');
|
var groups = require('../controllers/groups');
|
||||||
var auth = require('../controllers/auth');
|
var auth = require('../controllers/auth');
|
||||||
|
var challenges = require('../controllers/challenges');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
---------- /api/v1 API ------------
|
---------- /api/v1 API ------------
|
||||||
@@ -36,6 +37,7 @@ router["delete"]('/user/task/:id', auth.auth, cron, verifyTaskExists, user.delet
|
|||||||
router.post('/user/task', auth.auth, cron, user.createTask);
|
router.post('/user/task', auth.auth, cron, user.createTask);
|
||||||
router.put('/user/task/:id/sort', auth.auth, cron, verifyTaskExists, user.sortTask);
|
router.put('/user/task/:id/sort', auth.auth, cron, verifyTaskExists, user.sortTask);
|
||||||
router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted);
|
router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted);
|
||||||
|
router.post('/user/task/:id/unlink', auth.auth, challenges.unlink); // removing cron since they may want to remove task first
|
||||||
|
|
||||||
/* Items*/
|
/* Items*/
|
||||||
router.post('/user/buy/:type', auth.auth, cron, user.buy);
|
router.post('/user/buy/:type', auth.auth, cron, user.buy);
|
||||||
@@ -78,4 +80,15 @@ router.get('/members/:uid', groups.getMember);
|
|||||||
// Market
|
// Market
|
||||||
router.post('/market/buy', auth.auth, user.marketBuy);
|
router.post('/market/buy', auth.auth, user.marketBuy);
|
||||||
|
|
||||||
|
/* Challenges */
|
||||||
|
// Note: while challenges belong to groups, and would therefore make sense as a nested resource
|
||||||
|
// (eg /groups/:gid/challenges/:cid), they will also be referenced by users from the "challenges" tab
|
||||||
|
// without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource
|
||||||
|
router.get('/challenges', auth.auth, challenges.get)
|
||||||
|
router.post('/challenges', auth.auth, challenges.create)
|
||||||
|
router.post('/challenges/:cid', auth.auth, challenges.update)
|
||||||
|
router['delete']('/challenges/:cid', auth.auth, challenges['delete'])
|
||||||
|
router.post('/challenges/:cid/join', auth.auth, challenges.join)
|
||||||
|
router.post('/challenges/:cid/leave', auth.auth, challenges.leave)
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -11,14 +11,6 @@ router.get('/', function(req, res) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/partials/tasks', function(req, res) {
|
|
||||||
res.render('tasks/index', {env: res.locals.habitrpg});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/partials/options', function(req, res) {
|
|
||||||
res.render('options', {env: res.locals.habitrpg});
|
|
||||||
});
|
|
||||||
|
|
||||||
// -------- Marketing --------
|
// -------- Marketing --------
|
||||||
|
|
||||||
router.get('/splash.html', function(req, res) {
|
router.get('/splash.html', function(req, res) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ process.on("uncaughtException", function(error) {
|
|||||||
mongoose = require('mongoose');
|
mongoose = require('mongoose');
|
||||||
require('./models/user'); //load up the user schema - TODO is this necessary?
|
require('./models/user'); //load up the user schema - TODO is this necessary?
|
||||||
require('./models/group');
|
require('./models/group');
|
||||||
|
require('./models/challenge');
|
||||||
mongoose.connect(nconf.get('NODE_DB_URI'), function(err) {
|
mongoose.connect(nconf.get('NODE_DB_URI'), function(err) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
console.info('Connected with Mongoose');
|
console.info('Connected with Mongoose');
|
||||||
|
|||||||
@@ -30,14 +30,15 @@ html
|
|||||||
script(type='text/javascript', src='/bower_components/jquery.cookie/jquery.cookie.js')
|
script(type='text/javascript', src='/bower_components/jquery.cookie/jquery.cookie.js')
|
||||||
script(type='text/javascript', src='/bower_components/bootstrap-growl/jquery.bootstrap-growl.min.js')
|
script(type='text/javascript', src='/bower_components/bootstrap-growl/jquery.bootstrap-growl.min.js')
|
||||||
script(type='text/javascript', src='/bower_components/bootstrap-tour/build/js/bootstrap-tour.min.js')
|
script(type='text/javascript', src='/bower_components/bootstrap-tour/build/js/bootstrap-tour.min.js')
|
||||||
script(type='text/javascript', src='/bower_components/angular/angular.min.js')
|
script(type='text/javascript', src='/bower_components/angular/angular.js')
|
||||||
|
script(type='text/javascript', src='/bower_components/angular-ui-router/release/angular-ui-router.js')
|
||||||
|
|
||||||
script(type='text/javascript', src='/bower_components/angular-sanitize/angular-sanitize.min.js')
|
script(type='text/javascript', src='/bower_components/angular-sanitize/angular-sanitize.min.js')
|
||||||
script(type='text/javascript', src='/bower_components/marked/lib/marked.js')
|
script(type='text/javascript', src='/bower_components/marked/lib/marked.js')
|
||||||
|
|
||||||
script(type='text/javascript', src='/bower_components/angular-route/angular-route.min.js')
|
script(type='text/javascript', src='/bower_components/angular-route/angular-route.js')
|
||||||
script(type='text/javascript', src='/bower_components/angular-resource/angular-resource.min.js')
|
script(type='text/javascript', src='/bower_components/angular-resource/angular-resource.js')
|
||||||
script(type='text/javascript', src='/bower_components/angular-ui/build/angular-ui.min.js')
|
script(type='text/javascript', src='/bower_components/angular-ui/build/angular-ui.js')
|
||||||
script(type='text/javascript', src='/bower_components/angular-ui-utils/modules/keypress/keypress.js')
|
script(type='text/javascript', src='/bower_components/angular-ui-utils/modules/keypress/keypress.js')
|
||||||
// we'll remove this once angular-bootstrap is fixed
|
// we'll remove this once angular-bootstrap is fixed
|
||||||
script(type='text/javascript', src='/bower_components/bootstrap/docs/assets/js/bootstrap.min.js')
|
script(type='text/javascript', src='/bower_components/bootstrap/docs/assets/js/bootstrap.min.js')
|
||||||
@@ -59,6 +60,7 @@ html
|
|||||||
script(type='text/javascript', src='/js/services/groupServices.js')
|
script(type='text/javascript', src='/js/services/groupServices.js')
|
||||||
script(type='text/javascript', src='/js/services/memberServices.js')
|
script(type='text/javascript', src='/js/services/memberServices.js')
|
||||||
script(type='text/javascript', src='/js/services/guideServices.js')
|
script(type='text/javascript', src='/js/services/guideServices.js')
|
||||||
|
script(type='text/javascript', src='/js/services/challengeServices.js')
|
||||||
|
|
||||||
script(type='text/javascript', src='/js/filters/filters.js')
|
script(type='text/javascript', src='/js/filters/filters.js')
|
||||||
|
|
||||||
@@ -71,7 +73,6 @@ html
|
|||||||
script(type='text/javascript', src='/js/controllers/settingsCtrl.js')
|
script(type='text/javascript', src='/js/controllers/settingsCtrl.js')
|
||||||
script(type='text/javascript', src='/js/controllers/statsCtrl.js')
|
script(type='text/javascript', src='/js/controllers/statsCtrl.js')
|
||||||
script(type='text/javascript', src='/js/controllers/tasksCtrl.js')
|
script(type='text/javascript', src='/js/controllers/tasksCtrl.js')
|
||||||
script(type='text/javascript', src='/js/controllers/taskDetailsCtrl.js')
|
|
||||||
script(type='text/javascript', src='/js/controllers/filtersCtrl.js')
|
script(type='text/javascript', src='/js/controllers/filtersCtrl.js')
|
||||||
script(type='text/javascript', src='/js/controllers/userCtrl.js')
|
script(type='text/javascript', src='/js/controllers/userCtrl.js')
|
||||||
script(type='text/javascript', src='/js/controllers/groupsCtrl.js')
|
script(type='text/javascript', src='/js/controllers/groupsCtrl.js')
|
||||||
@@ -79,6 +80,7 @@ html
|
|||||||
script(type='text/javascript', src='/js/controllers/inventoryCtrl.js')
|
script(type='text/javascript', src='/js/controllers/inventoryCtrl.js')
|
||||||
script(type='text/javascript', src='/js/controllers/marketCtrl.js')
|
script(type='text/javascript', src='/js/controllers/marketCtrl.js')
|
||||||
script(type='text/javascript', src='/js/controllers/footerCtrl.js')
|
script(type='text/javascript', src='/js/controllers/footerCtrl.js')
|
||||||
|
script(type='text/javascript', src='/js/controllers/challengesCtrl.js')
|
||||||
-}
|
-}
|
||||||
|
|
||||||
//webfonts
|
//webfonts
|
||||||
@@ -91,6 +93,10 @@ html
|
|||||||
include ./shared/modals/index
|
include ./shared/modals/index
|
||||||
include ./shared/header/header
|
include ./shared/header/header
|
||||||
|
|
||||||
|
include ./shared/tasks/lists
|
||||||
|
include ./main/index
|
||||||
|
include ./options/index
|
||||||
|
|
||||||
#notification-area(ng-controller='NotificationCtrl')
|
#notification-area(ng-controller='NotificationCtrl')
|
||||||
#wrap
|
#wrap
|
||||||
|
|
||||||
@@ -108,7 +114,7 @@ html
|
|||||||
|
|
||||||
.exp-chart(ng-show='charts.exp')
|
.exp-chart(ng-show='charts.exp')
|
||||||
|
|
||||||
#main(ng-view)
|
#main(ui-view)
|
||||||
|
|
||||||
include ./shared/footer
|
include ./shared/footer
|
||||||
|
|
||||||
|
|||||||
4
views/main/index.jade
Normal file
4
views/main/index.jade
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
script(id='partials/main.html', type="text/ng-template")
|
||||||
|
include ./filters
|
||||||
|
div(ng-controller='TasksCtrl')
|
||||||
|
habitrpg-tasks(main='true', obj='user')
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
<main:>
|
|
||||||
<div>
|
|
||||||
<ul class="nav nav-tabs">
|
|
||||||
<li class="active"><a data-toggle='tab' data-target="#challengesViewParty">Party</a></li>
|
|
||||||
<li><a data-toggle='tab' data-target="#challengesViewGuild">Guild</a></li>
|
|
||||||
<li><a data-toggle='tab' data-target="#challengesViewPublic">Public</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content">
|
|
||||||
|
|
||||||
<div class="tab-pane active" id="challengesViewParty">
|
|
||||||
{{#unless _party.id}}
|
|
||||||
Join a party first.
|
|
||||||
{{else}}
|
|
||||||
{#if _challenge.new}
|
|
||||||
<app:challenges:create-form />
|
|
||||||
{else}
|
|
||||||
<!-- FIXME https://github.com/codeparty/derby/issues/267, see _guilds.challenges below -->
|
|
||||||
<app:challenges:create-button type='party' gid={{_party.id}} text='Party'/>
|
|
||||||
{#each _party.challenges as :challenge}
|
|
||||||
<app:challenges:listing challenge={:challenge} />
|
|
||||||
{/}
|
|
||||||
{/}
|
|
||||||
{{/}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane" id="challengesViewGuild">
|
|
||||||
<ul class="nav nav-pills">
|
|
||||||
{{#each _guilds as :guild}}
|
|
||||||
<li class="{{#if equal($index,0)}}active{{/}}"><a data-toggle='tab' data-target="#challenges-guild-{:guild.id}">{{:guild.name}}</a></li>
|
|
||||||
{{/}}
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content">
|
|
||||||
{{#each _guilds as :guild}}
|
|
||||||
<div class="tab-pane {{#if equal($index,0)}}active{{/}}" id="challenges-guild-{:guild.id}">
|
|
||||||
{#if _challenge.new}
|
|
||||||
<app:challenges:create-form />
|
|
||||||
{else}
|
|
||||||
<app:challenges:create-button type='guild' gid={{:guild.id}} text='Guild' />
|
|
||||||
{#each :guild.challenges as :challenge}
|
|
||||||
<app:challenges:listing challenge={groups[:guild.id].challenges[$index]} />
|
|
||||||
{/}
|
|
||||||
<hr/>
|
|
||||||
{/}
|
|
||||||
</div>
|
|
||||||
{{/}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane" id="challengesViewPublic">
|
|
||||||
{#if _challenge.new}
|
|
||||||
<app:challenges:create-form />
|
|
||||||
{else}
|
|
||||||
<app:challenges:create-button type='public' gid='habitrpg' text='Public' />
|
|
||||||
{#each _habitRPG.challenges as :challenge}
|
|
||||||
<app:challenges:listing challenge={groups.habitrpg.challenges[$index]} />
|
|
||||||
{/}
|
|
||||||
{/}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<listing:>
|
|
||||||
<div class="accordion-group">
|
|
||||||
<div class="accordion-heading">
|
|
||||||
<ul class='pull-right challenge-accordion-header-specs'>
|
|
||||||
<li>
|
|
||||||
{count(@challenge.users)} Subscribers
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<!-- prize -->
|
|
||||||
{#if @challenge.prize}
|
|
||||||
<table><tr><td>{@challenge.prize}</td><td><span class="Pet_Currency_Gem1x"></span></td><td> Prize</td></tr></table>
|
|
||||||
{/}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<!-- subscribe / unsubscribe -->
|
|
||||||
<a x-bind="click:challengeUnsubscribe" class='btn btn-small btn-danger {#unless indexOf(_user.challenges,@challenge.id)}hidden{/}'><i class='icon-ban-circle'></i> Unsubscribe</a>
|
|
||||||
<a x-bind="click:challengeSubscribe" class='btn btn-small btn-success {#if indexOf(_user.challenges,@challenge.id)}hidden{/}'><i class='icon-ok'></i> Subscribe</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<a class="accordion-toggle" data-toggle="collapse" href="#accordion-challenge-{{@challenge.id}}">{@challenge.name} (by {@challenge.user})</a>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div id="accordion-challenge-{{@challenge.id}}" class="accordion-body collapse">
|
|
||||||
<div class="accordion-inner">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Edit button -->
|
|
||||||
<span style='position:absolute; right:0;'>
|
|
||||||
{#if and(not(_editing.challenges[@challenge.id]),equal(@challenge.uid,_user.id))}
|
|
||||||
<ul class='nav nav-pills'><li>
|
|
||||||
<a x-bind='click:toggleChallengeEdit' data-id={{@challenge.id}} ><i class=icon-pencil></i></a>
|
|
||||||
</li></ul>
|
|
||||||
{else}
|
|
||||||
<ul class='nav nav-pills'><li>
|
|
||||||
<a x-bind='click:toggleChallengeEdit' data-id={{@challenge.id}} ><i class=icon-ok></i></a>
|
|
||||||
</li></ul>
|
|
||||||
{/}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{#if _editing.challenges[@challenge.id]}
|
|
||||||
<div class='-options'>
|
|
||||||
<input type=text class='option-content' value={@challenge.name} />
|
|
||||||
<textarea cols=3 class='option-content' placeholder='Description'>{@challenge.description}</textarea>
|
|
||||||
<input type=number class='option-content' placeholder='Gems Prize' value={@challenge.prize} />
|
|
||||||
</div>
|
|
||||||
{{#with @challenge}}
|
|
||||||
<a class='btn btn-small btn-danger' x-bind=click:removeAt >Delete</a>
|
|
||||||
{{/}}
|
|
||||||
{/}
|
|
||||||
{#if @challenge.description}<div>{@challenge.description}</div>{/}
|
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<app:tasks:task-lists
|
|
||||||
editable={_editing.challenges[@challenge.id]}
|
|
||||||
habits={@challenge.habits}
|
|
||||||
dailys={@challenge.dailys}
|
|
||||||
todos={@challenge.todos}
|
|
||||||
rewards={@challenge.rewards} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Statistics</h3>
|
|
||||||
{#each @challenge.users as :member}
|
|
||||||
<h4>{:member.name}</h4>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="module">
|
|
||||||
<app:challenges:stats header=Habits challenge={@challenge} member={:member} taskType=habit />
|
|
||||||
</div>
|
|
||||||
<div class="module">
|
|
||||||
<app:challenges:stats header=Dailies challenge={@challenge} member={:member} taskType=daily />
|
|
||||||
</div>
|
|
||||||
<div class="module">
|
|
||||||
<app:challenges:stats header=Todos challenge={@challenge} member={:member} taskType=todos />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<stats:>
|
|
||||||
<h5>{@header}</h5>
|
|
||||||
<div>
|
|
||||||
{#each @challenge[@taskType]s as :task}
|
|
||||||
<table><tr>
|
|
||||||
<td>
|
|
||||||
<!-- FIXME commented section below isn't getting updated dynamically, temp solution is less efficient -->
|
|
||||||
<strong>{:task.text}</strong>: {challengeMemberScore(@member,@taskType,:task.id)} <!--{round(@member[@taskType]s[:task.id].value)}-->
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div style='margin-left: 10px' class="challenge-{{@challenge.id}}-member-{{@member.id}}-history-{{:task.id}}"></div>
|
|
||||||
</td>
|
|
||||||
</tr></table>
|
|
||||||
{/}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<create-button:>
|
|
||||||
<a x-bind='click:challengeCreate' class='btn btn-success' data-type={{@type}} data-gid={{@gid}} >Create {{@text}} Challenge</a>
|
|
||||||
|
|
||||||
<create-form:>
|
|
||||||
<form x-bind="submit:challengeSave">
|
|
||||||
<div>
|
|
||||||
<input type='submit' class='btn btn-success' value='Save' />
|
|
||||||
<input type='button' x-bind='click:challengeDiscard' class='btn btn-danger' value=Discard />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='challenge-options'>
|
|
||||||
<input type='text' class='option-content' value={_challenge.new.name} placeholder="Challenge Title" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--<fieldset>
|
|
||||||
<div>
|
|
||||||
<select>
|
|
||||||
<option selected="{equal('party',_challenge.new.group.type)}" >Party</option>
|
|
||||||
<option selected="{equal('guild',_challenge.new.group.type)}" >Guild</option>
|
|
||||||
<option selected="{equal('public',_challenge.new.group.type)}" >Public</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{#if equal(_challenge.new.assignTo,'Party')}
|
|
||||||
<div class='row-fluid'>
|
|
||||||
<div class='span4 well'>
|
|
||||||
<div><input type='radio' name='challenge-party-selection' checked={_challenge.new.partyAssignees} >All Party</input></div>
|
|
||||||
<small>No individual privacy on the challenge, all party members can see progress even if they decline the challenge. Any new party members can subscribe to this challenge.</small>
|
|
||||||
</div>
|
|
||||||
<div class='span8 well'>
|
|
||||||
<div><input type='radio' name='challenge-party-selection' checked={not(_challenge.new.partyAssignees)} >Individual Members</input></div>
|
|
||||||
<div>
|
|
||||||
<select multiple="multiple">
|
|
||||||
{{#each _party.members as :memberId}}
|
|
||||||
<option>{{username(_members[:memberId].auth,_members[:memberId].profile.name)}}</option>
|
|
||||||
{{/}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div><small>Only the invited party members can subscribe to this challenge. New party joins won't see this challenge.</small></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/}
|
|
||||||
{#if equal(_challenge.new.group.type,'guild')}
|
|
||||||
<select>
|
|
||||||
{{#each _guilds as :guild}}
|
|
||||||
<option selected="{equal(:guild.id,_challenge.new.group.id)}" >{:guild.name}</option>
|
|
||||||
{{/}}
|
|
||||||
</select>
|
|
||||||
{/}
|
|
||||||
</div>
|
|
||||||
</fieldset>-->
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<app:tasks:task-lists
|
|
||||||
habits={_challenge.new.habits}
|
|
||||||
dailys={_challenge.new.dailys}
|
|
||||||
todos={_challenge.new.todos}
|
|
||||||
rewards={_challenge.new.rewards}
|
|
||||||
editable=true />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
74
views/options/challenges.jade
Normal file
74
views/options/challenges.jade
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
script(type='text/ng-template', id='partials/options.challenges.html')
|
||||||
|
.row-fluid
|
||||||
|
.span2.well
|
||||||
|
h4 Filters
|
||||||
|
ul
|
||||||
|
li(ng-repeat='group in groups')
|
||||||
|
input(type='checkbox', ng-model='search.group')
|
||||||
|
| {{group.name}}
|
||||||
|
li
|
||||||
|
input(type='checkbox', ng-model='search.members')
|
||||||
|
| Subscribed (TODO)
|
||||||
|
li
|
||||||
|
input(type='checkbox', ng-model='search.members')
|
||||||
|
| Available (TODO)
|
||||||
|
.span10
|
||||||
|
// Creation form
|
||||||
|
a.btn.btn-success(ng-click='create()') Create Challenge
|
||||||
|
.create-challenge-from(ng-if='newChallenge')
|
||||||
|
form(ng-submit='save(newChallenge)')
|
||||||
|
div
|
||||||
|
input.btn.btn-success(type='submit', value='Save')
|
||||||
|
input.btn.btn-danger(type='button', ng-click='discard()', value='Discard')
|
||||||
|
select(ng-model='newChallenge.group', ng-required='required', name='Group', ng-options='g._id as g.name for g in groups')
|
||||||
|
.challenge-options
|
||||||
|
input.option-content(type='text', ng-model='newChallenge.name', placeholder='Challenge Title', required='required')
|
||||||
|
|
||||||
|
habitrpg-tasks(main=false, obj='newChallenge')
|
||||||
|
|
||||||
|
// Challenges list
|
||||||
|
.accordion-group(ng-repeat='challenge in challenges | filter:search', ng-init='challenge._locked=true')
|
||||||
|
.accordion-heading
|
||||||
|
ul.pull-right.challenge-accordion-header-specs
|
||||||
|
li {{challenge.members.length}} Subscribers
|
||||||
|
li(ng-show='challenge.prize')
|
||||||
|
// prize
|
||||||
|
table(ng-show='challenge.prize')
|
||||||
|
tr
|
||||||
|
td {{challenge.prize}}
|
||||||
|
td
|
||||||
|
span.Pet_Currency_Gem1x
|
||||||
|
td Prize
|
||||||
|
li
|
||||||
|
// subscribe / unsubscribe
|
||||||
|
a.btn.btn-small.btn-danger(ng-show='indexOf(challenge.members, user._id)', ng-click='clickUnsubscribe(challenge, $event)')
|
||||||
|
i.icon-ban-circle
|
||||||
|
| Unsubscribe
|
||||||
|
a.btn.btn-small.btn-success(ng-hide='indexOf(challenge.members, user._id)', ng-click='challenge.$join()')
|
||||||
|
i.icon-ok
|
||||||
|
| Subscribe
|
||||||
|
a.accordion-toggle(data-toggle='collapse', data-target='#accordion-challenge-{{challenge._id}}') {{challenge.name}} (by {{challenge.leader.name}})
|
||||||
|
.accordion-body.collapse(id='accordion-challenge-{{challenge._id}}')
|
||||||
|
.accordion-inner
|
||||||
|
// Edit button
|
||||||
|
ul.unstyled()
|
||||||
|
li(ng-show='challenge.leader==user._id && challenge._locked')
|
||||||
|
button.btn.btn-default(ng-click='challenge._locked = false') Edit
|
||||||
|
li(ng-hide='challenge._locked')
|
||||||
|
button.btn.btn-primary(ng-click='save(challenge)') Save
|
||||||
|
button.btn.btn-danger(ng-click='delete(challenge)') Delete
|
||||||
|
button.btn.btn-default(ng-click='challenge._locked=true') Cancel
|
||||||
|
|
||||||
|
div(ng-hide='challenge._locked')
|
||||||
|
.-options
|
||||||
|
input.option-content(type='text', ng-model='challenge.name')
|
||||||
|
textarea.option-content(cols='3', placeholder='Description', ng-model='challenge.description')
|
||||||
|
// <input type=number class='option-content' placeholder='Gems Prize' value={@challenge.prize} />
|
||||||
|
hr
|
||||||
|
|
||||||
|
div(ng-if='challenge.description') {{challenge.description}}
|
||||||
|
habitrpg-tasks(obj='challenge', main=false)
|
||||||
|
h3 Statistics
|
||||||
|
div(ng-repeat='member in challenge.members', ng-init='member._locked = true')
|
||||||
|
h4 {{member.profile.name}}
|
||||||
|
habitrpg-tasks(main=false, obj='member')
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
li(ng-repeat='message in group.chat', ng-class='{highlight: message.text.indexOf(username(user.auth,user.profile.name)) != -1}')
|
li(ng-repeat='message in group.chat', ng-class='{highlight: indexOf(message.text, user.profile.name)}')
|
||||||
a.label.chat-message(class='{{nameTagClasses(message)}}', tooltip='{{message.contributor}}', ng-click='clickMember(message.uuid, true)')
|
a.label.chat-message(class='{{nameTagClasses(message)}}', tooltip='{{message.contributor}}', ng-click='clickMember(message.uuid, true)')
|
||||||
| {{message.user}}
|
| {{message.user}}
|
||||||
span
|
span
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank'
|
|||||||
i.icon-remove
|
i.icon-remove
|
||||||
|
|
||||||
h4 Assign Group Leader
|
h4 Assign Group Leader
|
||||||
select#group-leader-selection(ng-model='group._newLeader', ng-options='username(member.auth, member.profile.name) for member in group.members')
|
select#group-leader-selection(ng-model='group._newLeader', ng-options='member.profile.name for member in group.members')
|
||||||
|
|
||||||
div(ng-show='!group._editing')
|
div(ng-show='!group._editing')
|
||||||
img.pull-right(ng-show='group.logo', style='max-width:150px', ng-src='{{group.logo}}')
|
img.pull-right(ng-show='group.logo', style='max-width:150px', ng-src='{{group.logo}}')
|
||||||
@@ -61,7 +61,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank'
|
|||||||
i.icon-ban-circle(tooltip='Boot Member')
|
i.icon-ban-circle(tooltip='Boot Member')
|
||||||
a
|
a
|
||||||
span(ng-class='{"badge badge-info": group.leader==member._id}', ng-click='clickMember(member._id, true)')
|
span(ng-class='{"badge badge-info": group.leader==member._id}', ng-click='clickMember(member._id, true)')
|
||||||
| {{username(member.auth, member.profile.name)}}
|
| {{member.profile.name}}
|
||||||
td ({{member._id}})
|
td ({{member._id}})
|
||||||
h4(ng-show='group.invites.length > 0') Invited
|
h4(ng-show='group.invites.length > 0') Invited
|
||||||
table.table.table-striped
|
table.table.table-striped
|
||||||
@@ -82,25 +82,22 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank'
|
|||||||
input.option-content(type='text', placeholder='User Id', ng-model='group.invitee')
|
input.option-content(type='text', placeholder='User Id', ng-model='group.invitee')
|
||||||
input.btn(type='submit', value='Invite')
|
input.btn(type='submit', value='Invite')
|
||||||
|
|
||||||
|
.modal(style='position: relative;top: auto;left: auto;right: auto;margin: 0 auto 20px;z-index: 1;max-width: 100%;')
|
||||||
|
.modal-header
|
||||||
|
h3 Challenges
|
||||||
|
.modal-body
|
||||||
|
a(target='_blank', href='https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58') Details
|
||||||
|
div(ng-show='group.challenges')
|
||||||
|
table.table.table-striped
|
||||||
|
tr(ng-repeat='challenge in group.challenges')
|
||||||
|
td
|
||||||
|
| {{challenge.name}}
|
||||||
|
p.
|
||||||
|
Visit the <span class=label><i class=icon-bullhorn></i> Challenges</span> for more information.
|
||||||
|
div(ng-hid='group.challenges')
|
||||||
|
p.
|
||||||
|
No challenges yet, visit the <span class=label><i class=icon-bullhorn></i> Challenges</span> tab to create one.
|
||||||
|
|
||||||
//-accordion-group(heading='Challenges')
|
|
||||||
span.label
|
|
||||||
i.icon-bullhorn
|
|
||||||
| Challenges
|
|
||||||
| coming soon!
|
|
||||||
a(target='_blank', href='https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58') Details
|
|
||||||
//-{#if group.challenges}
|
|
||||||
//- <table class="table table-striped">
|
|
||||||
//- {#each group.challenges as :challenge}
|
|
||||||
//- <tr><td>
|
|
||||||
//- {:challenge.name}
|
|
||||||
//- </td></tr>
|
|
||||||
//- {/}
|
|
||||||
//- </table>
|
|
||||||
//- Visit the <span class=label><i class=icon-bullhorn></i> Challenges</span> for more information.
|
|
||||||
//-{else}
|
|
||||||
//- No challenges yet, visit the <span class=label><i class=icon-bullhorn></i> Challenges</span> tab to create one.
|
|
||||||
//-{/}
|
|
||||||
a.btn.btn-danger(data-id='{{group.id}}', ng-click='leave(group)') Leave
|
a.btn.btn-danger(data-id='{{group.id}}', ng-click='leave(group)') Leave
|
||||||
|
|
||||||
.span8
|
.span8
|
||||||
@@ -115,7 +112,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank'
|
|||||||
td
|
td
|
||||||
.popover.static-popover.fade.right.in.wide-popover
|
.popover.static-popover.fade.right.in.wide-popover
|
||||||
.arrow
|
.arrow
|
||||||
h3.popover-title {{username(Members.members[group.leader].auth, Members.members[group.leader].profile.name)}}
|
h3.popover-title {{Members.members[group.leader].profile.name}}
|
||||||
.popover-content
|
.popover-content
|
||||||
markdown(ng-model='group.leaderMessage')
|
markdown(ng-model='group.leaderMessage')
|
||||||
div(ng-controller='ChatCtrl')
|
div(ng-controller='ChatCtrl')
|
||||||
|
|||||||
@@ -1,66 +1,87 @@
|
|||||||
// FIXME note, due to https://github.com/angular-ui/bootstrap/issues/783 we can't use nested angular-bootstrap tabs
|
// FIXME note, due to https://github.com/angular-ui/bootstrap/issues/783 we can't use nested angular-bootstrap tabs
|
||||||
// Subscribe to that ticket & change this when they fix
|
// Subscribe to that ticket & change this when they fix
|
||||||
|
|
||||||
ul.nav.nav-tabs
|
script(type='text/ng-template', id='partials/options.groups.tavern.html')
|
||||||
li.active
|
include ./tavern
|
||||||
a(data-target='#groups-party', data-toggle='tab') Party
|
|
||||||
li
|
|
||||||
a(data-target='#groups-guilds', data-toggle='tab', ng-click='fetchGuilds()') Guilds
|
|
||||||
|
|
||||||
.tab-content(ng-controller='PartyCtrl')
|
script(type='text/ng-template', id='partials/options.groups.party.html')
|
||||||
#groups-party.tab-pane.active
|
div(ng-show='group._id')
|
||||||
div(ng-show='group._id')
|
include ./group
|
||||||
include ./group
|
div(ng-hide='group._id')
|
||||||
div(ng-hide='group._id')
|
div(ng-show='user.invitations.party')
|
||||||
div(ng-show='user.invitations.party')
|
// #with required for the accept/reject buttons
|
||||||
// #with required for the accept/reject buttons
|
// {#with _user.invitations.party as :party}
|
||||||
// {#with _user.invitations.party as :party}
|
h2 You're Invited To {{user.invitations.party.name}}
|
||||||
h2 You're Invited To {{user.invitations.party.name}}
|
a.btn.btn-success(data-type='party', ng-click='join(user.invitations.party)') Accept
|
||||||
a.btn.btn-success(data-type='party', ng-click='join(user.invitations.party)') Accept
|
a.btn.btn-danger(ng-click='reject()') Reject
|
||||||
a.btn.btn-danger(ng-click='reject()') Reject
|
// {/}
|
||||||
// {/}
|
div(ng-hide='user.invitations.party', ng-controller='PartyCtrl')
|
||||||
div(ng-hide='user.invitations.party', ng-controller='PartyCtrl')
|
h2 Create A Party
|
||||||
h2 Create A Party
|
p
|
||||||
p
|
| You are not in a party. You can either create one and invite friends, or if you want to join an existing party, have them enter:
|
||||||
| You are not in a party. You can either create one and invite friends, or if you want to join an existing party, have them enter:
|
pre.prettyprint.
|
||||||
pre.prettyprint.
|
{{user.id}}
|
||||||
{{user.id}}
|
include ./create-group
|
||||||
include ./create-group
|
|
||||||
|
|
||||||
#groups-guilds.tab-pane(ng-controller='GuildsCtrl')
|
script(type='text/ng-template', id='partials/options.groups.guilds.public.html')
|
||||||
ul.nav.nav-tabs
|
div(ng-repeat='invitation in user.invitations.guilds')
|
||||||
li.active
|
h3 You're Invited To {{invitation.name}}
|
||||||
a(data-target='#groups-public-guilds', data-toggle='tab') Public Guilds
|
a.btn.btn-success(data-type='guild', ng-click='join(invitation)') Accept
|
||||||
li(ng-repeat='group in groups.guilds')
|
a.btn.btn-danger(ng-click='reject(invitation)') Reject
|
||||||
a(data-target='#groups-guild-{{group._id}}', data-toggle='tab') {{group.name}}
|
// Public Groups
|
||||||
li
|
.options-group.option-large.whatever-options
|
||||||
a(data-target='#groups-create-guild', data-toggle='tab') Create Guild
|
input.option-content(type='text',ng-model='guildSearch', placeholder='Search')
|
||||||
.tab-content
|
table.table.table-striped
|
||||||
.tab-pane.active#groups-public-guilds
|
tr(ng-repeat='group in groups.public | filter:guildSearch')
|
||||||
div(ng-repeat='invitation in user.invitations.guilds')
|
td
|
||||||
h3 You're Invited To {{invitation.name}}
|
ul.pull-right.challenge-accordion-header-specs
|
||||||
a.btn.btn-success(data-type='guild', ng-click='join(invitation)') Accept
|
li {{group.memberCount}} member(s)
|
||||||
a.btn.btn-danger(ng-click='reject(invitation)') Reject
|
li
|
||||||
// Public Groups
|
// join / leave
|
||||||
.options-group.option-large.whatever-options
|
a.btn.btn-small.btn-danger(ng-show='group_.isMember', ng-click='leave(group)')
|
||||||
input.option-content(type='text',ng-model='guildSearch', placeholder='Search')
|
i.icon-ban-circle
|
||||||
table.table.table-striped
|
| Leave
|
||||||
tr(ng-repeat='group in groups.public | filter:guildSearch')
|
a.btn.btn-small.btn-success(ng-hide='group._isMember', ng-click='join(group)')
|
||||||
td
|
i.icon-ok
|
||||||
ul.pull-right.challenge-accordion-header-specs
|
| Join
|
||||||
li {{group.members.length}} member(s)
|
h4 {{group.name}}
|
||||||
li
|
p {{group.description}}
|
||||||
// join / leave
|
|
||||||
a.btn.btn-small.btn-danger(ng-show='isMember(user, group)', ng-click='leave(group)')
|
|
||||||
i.icon-ban-circle
|
|
||||||
| Leave
|
|
||||||
a.btn.btn-small.btn-success(ng-hide='isMember(user, group)', ng-click='join(group)')
|
|
||||||
i.icon-ok
|
|
||||||
| Join
|
|
||||||
h4 {{group.name}}
|
|
||||||
p {{group.description}}
|
|
||||||
.tab-pane(id='groups-guild-{{group._id}}', ng-repeat='group in groups.guilds')
|
|
||||||
include ./group
|
|
||||||
|
|
||||||
.tab-pane#groups-create-guild
|
script(type='text/ng-template', id='partials/options.groups.guilds.detail.html')
|
||||||
include ./create-group
|
include ./group
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='partials/options.groups.guilds.create.html')
|
||||||
|
include ./create-group
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='partials/options.groups.guilds.html')
|
||||||
|
ul.nav.nav-tabs
|
||||||
|
li(ng-class="{ active: $state.includes('options.groups.guilds.public') }")
|
||||||
|
a(ui-sref='options.groups.guilds.public')
|
||||||
|
| Public Guilds
|
||||||
|
li(ng-class="{ active: $stateParams.gid == group._id }", ng-repeat='group in groups.guilds')
|
||||||
|
a(ui-sref="options.groups.guilds.detail({gid:group._id})")
|
||||||
|
| {{group.name}}
|
||||||
|
li(ng-class="{ active: $state.includes('options.groups.guilds.create') }")
|
||||||
|
a(ui-sref='options.groups.guilds.create')
|
||||||
|
| Create Guild
|
||||||
|
|
||||||
|
.tab-content
|
||||||
|
.tab-pane.active
|
||||||
|
div(ui-view)
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='partials/options.groups.html')
|
||||||
|
ul.nav.nav-tabs
|
||||||
|
li(ng-class="{ active: $state.includes('options.groups.tavern') }")
|
||||||
|
a(ui-sref='options.groups.tavern')
|
||||||
|
i.icon-eye-close
|
||||||
|
| Tavern
|
||||||
|
li(ng-class="{ active: $state.includes('options.groups.party') }")
|
||||||
|
a(ui-sref='options.groups.party')
|
||||||
|
| Party
|
||||||
|
li(ng-class="{ active: $state.includes('options.groups.guilds') }")
|
||||||
|
a(ui-sref='options.groups.guilds')
|
||||||
|
| Guilds
|
||||||
|
|
||||||
|
.tab-content
|
||||||
|
.tab-pane.active
|
||||||
|
div(ui-view)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
.row-fluid(ng-controller='TavernCtrl')
|
.row-fluid
|
||||||
.span4
|
.span4
|
||||||
.tavern-pane
|
.tavern-pane
|
||||||
table
|
table
|
||||||
|
|||||||
@@ -1,60 +1,37 @@
|
|||||||
.grid
|
include ./profile
|
||||||
.module.full-width
|
include ./groups/index
|
||||||
span.option-box.pull-right.wallet
|
include ./challenges
|
||||||
include ../shared/gems
|
include ./inventory/index
|
||||||
|
include ./settings
|
||||||
|
|
||||||
ul.nav.nav-tabs
|
script(id='partials/options.html', type="text/ng-template")
|
||||||
li.active
|
.grid
|
||||||
a(data-toggle='tab', data-target='#profile-tab')
|
.module.full-width
|
||||||
i.icon-user
|
span.option-box.pull-right.wallet
|
||||||
| Profile
|
include ../shared/gems
|
||||||
li
|
|
||||||
a(data-toggle='tab',data-target='#groups-tab', ng-click='fetchParty()')
|
|
||||||
i.icon-heart
|
|
||||||
| Groups
|
|
||||||
li(ng-show='user.flags.dropsEnabled')
|
|
||||||
a(data-toggle='tab',data-target='#inventory-tab')
|
|
||||||
i.icon-gift
|
|
||||||
| Inventory
|
|
||||||
li(ng-show='user.flags.dropsEnabled')
|
|
||||||
a(data-toggle='tab',data-target='#stable-tab')
|
|
||||||
i.icon-leaf
|
|
||||||
| Stable
|
|
||||||
li
|
|
||||||
a(data-toggle='tab',data-target='#tavern-tab',ng-click='fetchTavern()')
|
|
||||||
i.icon-eye-close
|
|
||||||
| Tavern
|
|
||||||
li
|
|
||||||
a(data-toggle='tab',data-target='#achievements-tab')
|
|
||||||
i.icon-certificate
|
|
||||||
| Achievements
|
|
||||||
|
|
||||||
li
|
ul.nav.nav-tabs
|
||||||
a(data-toggle='tab',data-target='#settings-tab')
|
li(ng-class="{ active: $state.includes('options.profile') }")
|
||||||
i.icon-wrench
|
a(ui-sref='options.profile')
|
||||||
| Settings
|
i.icon-user
|
||||||
|
| Profile
|
||||||
|
li(ng-class="{ active: $state.includes('options.groups') }")
|
||||||
|
a(ui-sref='options.groups')
|
||||||
|
i.icon-heart
|
||||||
|
| Groups
|
||||||
|
li(ng-class="{ active: $state.includes('options.inventory') }", ng-if='user.flags.dropsEnabled')
|
||||||
|
a(ui-sref='options.inventory')
|
||||||
|
i.icon-gift
|
||||||
|
| Inventory
|
||||||
|
li(ng-class="{ active: $state.includes('options.challenges') }")
|
||||||
|
a(ui-sref='options.challenges')
|
||||||
|
i.icon-bullhorn
|
||||||
|
| Challenges
|
||||||
|
li(ng-class="{ active: $state.includes('options.settings') }")
|
||||||
|
a(ui-sref='options.settings')
|
||||||
|
i.icon-wrench
|
||||||
|
| Settings
|
||||||
|
|
||||||
.tab-content
|
.tab-content
|
||||||
.tab-pane.active#profile-tab
|
.tab-pane.active
|
||||||
include ./profile
|
div(ui-view)
|
||||||
|
|
||||||
.tab-pane#groups-tab
|
|
||||||
include ./groups/index
|
|
||||||
|
|
||||||
.tab-pane#inventory-tab
|
|
||||||
include ./inventory
|
|
||||||
|
|
||||||
.tab-pane#stable-tab
|
|
||||||
include ./pets
|
|
||||||
|
|
||||||
.tab-pane#tavern-tab
|
|
||||||
include ./groups/tavern
|
|
||||||
|
|
||||||
.tab-pane#achievements-tab(ng-controller='UserCtrl')
|
|
||||||
include ../shared/profiles/achievements
|
|
||||||
|
|
||||||
// <li><a data-toggle='tab' data-target="#profile-challenges" id='profile-challenges-tab-link' ><i class='icon-bullhorn'></i> Challenges</a></li>
|
|
||||||
// app:challenges:main
|
|
||||||
|
|
||||||
.tab-pane#settings-tab
|
|
||||||
include ./settings
|
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
.row-fluid(ng-controller='InventoryCtrl')
|
|
||||||
.span6.border-right
|
|
||||||
h2 Inventory
|
|
||||||
p.well Combine eggs with hatching potions to make pets by clicking an egg and then a potion. Sell unwanted drops to Alexander the Merchant.
|
|
||||||
menu.inventory-list(type='list')
|
|
||||||
li.customize-menu
|
|
||||||
menu.pets-menu(label='Eggs ({{userEggs.length}})')
|
|
||||||
p(ng-show='userEggs.length < 1') You don't have any eggs yet.
|
|
||||||
div(ng-repeat='egg in userEggs track by $index')
|
|
||||||
button.customize-option(tooltip='{{egg.text}}', ng-click='chooseEgg(egg, $index)', class='Pet_Egg_{{egg.name}}', ng-class='selectableInventory(egg, selectedPotion.name, $index)')
|
|
||||||
p {{egg.text}}
|
|
||||||
li.customize-menu
|
|
||||||
menu.hatchingPotion-menu(label='Hatching Potions ({{userHatchingPotions.length}})')
|
|
||||||
p(ng-show='userHatchingPotions.length < 1') You don't have any hatching potions yet.
|
|
||||||
div(ng-repeat='hatchingPotion in userHatchingPotions track by $index')
|
|
||||||
button.customize-option(tooltip='{{hatchingPotion}}', ng-click='choosePotion(hatchingPotion, $index)', class='Pet_HatchingPotion_{{hatchingPotion}}', ng-class='selectableInventory(selectedEgg, hatchingPotion, $index)')
|
|
||||||
p {{hatchingPotion}}
|
|
||||||
|
|
||||||
.span6
|
|
||||||
h2 Market
|
|
||||||
.row-fluid(ng-controller='MarketCtrl')
|
|
||||||
table.NPC-Alex-container
|
|
||||||
tr
|
|
||||||
td
|
|
||||||
.NPC-Alex.pull-left
|
|
||||||
td
|
|
||||||
.popover.static-popover.fade.right.in
|
|
||||||
.arrow
|
|
||||||
h3.popover-title
|
|
||||||
a(target='_blank', href='http://www.kickstarter.com/profile/523661924') Alexander the Merchant
|
|
||||||
.popover-content
|
|
||||||
p
|
|
||||||
| Welcome to the Market. Dying to get that particular pet you're after, but don't want to wait for it to drop? Buy it here! If you have unwanted drops, sell them to me.
|
|
||||||
p
|
|
||||||
button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')
|
|
||||||
| Sell {{selectedEgg.name}} for {{selectedEgg.value}} GP
|
|
||||||
button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')
|
|
||||||
| Sell {{selectedPotion.name}} for {{selectedPotion.value}} GP
|
|
||||||
|
|
||||||
menu.inventory-list(type='list')
|
|
||||||
li.customize-menu
|
|
||||||
menu.pets-menu(label='Eggs')
|
|
||||||
div(ng-repeat='egg in eggs track by $index')
|
|
||||||
button.customize-option(tooltip='{{egg.text}} - {{egg.value}} Gem(s)', ng-click='buy("egg", egg)', class='Pet_Egg_{{egg.name}}')
|
|
||||||
p {{egg.text}}
|
|
||||||
|
|
||||||
li.customize-menu
|
|
||||||
menu.pets-menu(label='Hatching Potions')
|
|
||||||
div(ng-repeat='hatchingPotion in hatchingPotions track by $index')
|
|
||||||
button.customize-option(tooltip='{{hatchingPotion.text}} - {{hatchingPotion.value}} Gem(s)', ng-click='buy("hatchingPotion", hatchingPotion)', class='Pet_HatchingPotion_{{hatchingPotion.name}}')
|
|
||||||
p {{hatchingPotion.text}}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
15
views/options/inventory/index.jade
Normal file
15
views/options/inventory/index.jade
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
include ./inventory
|
||||||
|
include ./stable
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='partials/options.inventory.html')
|
||||||
|
ul.nav.nav-tabs
|
||||||
|
li(ng-class="{ active: $state.includes('options.inventory.inventory') }")
|
||||||
|
a(ui-sref='options.inventory.inventory')
|
||||||
|
| Inventory
|
||||||
|
li(ng-class="{ active: $state.includes('options.inventory.stable') }")
|
||||||
|
a(ui-sref='options.inventory.stable')
|
||||||
|
| Stable
|
||||||
|
|
||||||
|
.tab-content
|
||||||
|
.tab-pane.active
|
||||||
|
div(ui-view)
|
||||||
55
views/options/inventory/inventory.jade
Normal file
55
views/options/inventory/inventory.jade
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
script(type='text/ng-template', id='partials/options.inventory.inventory.html')
|
||||||
|
.row-fluid(ng-controller='InventoryCtrl')
|
||||||
|
.span6.border-right
|
||||||
|
h2 Inventory
|
||||||
|
p.well Combine eggs with hatching potions to make pets by clicking an egg and then a potion. Sell unwanted drops to Alexander the Merchant.
|
||||||
|
menu.inventory-list(type='list')
|
||||||
|
li.customize-menu
|
||||||
|
menu.pets-menu(label='Eggs ({{userEggs.length}})')
|
||||||
|
p(ng-show='userEggs.length < 1') You don't have any eggs yet.
|
||||||
|
div(ng-repeat='egg in userEggs track by $index')
|
||||||
|
button.customize-option(tooltip='{{egg.text}}', ng-click='chooseEgg(egg, $index)', class='Pet_Egg_{{egg.name}}', ng-class='selectableInventory(egg, selectedPotion.name, $index)')
|
||||||
|
p {{egg.text}}
|
||||||
|
li.customize-menu
|
||||||
|
menu.hatchingPotion-menu(label='Hatching Potions ({{userHatchingPotions.length}})')
|
||||||
|
p(ng-show='userHatchingPotions.length < 1') You don't have any hatching potions yet.
|
||||||
|
div(ng-repeat='hatchingPotion in userHatchingPotions track by $index')
|
||||||
|
button.customize-option(tooltip='{{hatchingPotion}}', ng-click='choosePotion(hatchingPotion, $index)', class='Pet_HatchingPotion_{{hatchingPotion}}', ng-class='selectableInventory(selectedEgg, hatchingPotion, $index)')
|
||||||
|
p {{hatchingPotion}}
|
||||||
|
|
||||||
|
.span6
|
||||||
|
h2 Market
|
||||||
|
.row-fluid(ng-controller='MarketCtrl')
|
||||||
|
table.NPC-Alex-container
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
.NPC-Alex.pull-left
|
||||||
|
td
|
||||||
|
.popover.static-popover.fade.right.in
|
||||||
|
.arrow
|
||||||
|
h3.popover-title
|
||||||
|
a(target='_blank', href='http://www.kickstarter.com/profile/523661924') Alexander the Merchant
|
||||||
|
.popover-content
|
||||||
|
p
|
||||||
|
| Welcome to the Market. Dying to get that particular pet you're after, but don't want to wait for it to drop? Buy it here! If you have unwanted drops, sell them to me.
|
||||||
|
p
|
||||||
|
button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')
|
||||||
|
| Sell {{selectedEgg.name}} for {{selectedEgg.value}} GP
|
||||||
|
button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')
|
||||||
|
| Sell {{selectedPotion.name}} for {{selectedPotion.value}} GP
|
||||||
|
|
||||||
|
menu.inventory-list(type='list')
|
||||||
|
li.customize-menu
|
||||||
|
menu.pets-menu(label='Eggs')
|
||||||
|
div(ng-repeat='egg in eggs track by $index')
|
||||||
|
button.customize-option(tooltip='{{egg.text}} - {{egg.value}} Gem(s)', ng-click='buy("egg", egg)', class='Pet_Egg_{{egg.name}}')
|
||||||
|
p {{egg.text}}
|
||||||
|
|
||||||
|
li.customize-menu
|
||||||
|
menu.pets-menu(label='Hatching Potions')
|
||||||
|
div(ng-repeat='hatchingPotion in hatchingPotions track by $index')
|
||||||
|
button.customize-option(tooltip='{{hatchingPotion.text}} - {{hatchingPotion.value}} Gem(s)', ng-click='buy("hatchingPotion", hatchingPotion)', class='Pet_HatchingPotion_{{hatchingPotion.name}}')
|
||||||
|
p {{hatchingPotion.text}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
30
views/options/inventory/stable.jade
Normal file
30
views/options/inventory/stable.jade
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
script(type='text/ng-template', id='partials/options.inventory.stable.html')
|
||||||
|
.stable(ng-controller='PetsCtrl')
|
||||||
|
.NPC-Matt
|
||||||
|
.popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;')
|
||||||
|
.arrow
|
||||||
|
h3.popover-title
|
||||||
|
a(target='_blank', href='http://www.kickstarter.com/profile/mattboch') Matt Boch
|
||||||
|
.popover-content
|
||||||
|
p
|
||||||
|
| Welcome to the Stable! I'm Matt, the beast master. Choose a pet here to adventure at your side - they aren't much help yet, but I forsee a time when they're able to
|
||||||
|
a(href='https://trello.com/card/mounts/50e5d3684fe3a7266b0036d6/221') grow into powerful steeds
|
||||||
|
| ! Until that day,
|
||||||
|
a(target='_blank', href='https://f.cloud.github.com/assets/2374703/164631/3ed5fa6c-78cd-11e2-8743-f65ac477b55e.png') have a look-see
|
||||||
|
| at all the pets you can collect.
|
||||||
|
h4 {{userPets.length}} / {{totalPets}} Pets Found
|
||||||
|
|
||||||
|
menu.pets(type='list')
|
||||||
|
li.customize-menu(ng-repeat='pet in pets')
|
||||||
|
menu
|
||||||
|
div(ng-repeat='potion in hatchingPotions', tooltip='{{potion.name}} {{pet.name}}')
|
||||||
|
button(class="pet-button Pet-{{pet.name}}-{{potion.name}}", ng-show='hasPet(pet.name, potion.name)', ng-class="{active: isCurrentPet(pet.name, potion.name)}", ng-click='choosePet(pet.name, potion.name)')
|
||||||
|
button(class="pet-button pet-not-owned", ng-hide='hasPet(pet.name, potion.name)')
|
||||||
|
img(src='/bower_components/habitrpg-shared/img/PixelPaw.png')
|
||||||
|
|
||||||
|
h4 Rare Pets
|
||||||
|
menu
|
||||||
|
div(ng-if='hasPet("Wolf", "Veteran")')
|
||||||
|
button(class="pet-button Pet-Wolf-Veteran", ng-class='{active: isCurrentPet("Wolf", "Veteran")}', ng-click='choosePet("Wolf", "Veteran")', tooltip='Veteran Wolf')
|
||||||
|
div(ng-if='hasPet("Wolf", "Cerberus")')
|
||||||
|
button(class="pet-button Pet-Wolf-Cerberus", ng-class='{active: isCurrentPet("Wolf", "Cerberus")}', ng-click='choosePet("Wolf", "Cerberus")', tooltip='Cerberus Pup')
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
.stable(ng-controller='PetsCtrl')
|
|
||||||
.NPC-Matt
|
|
||||||
.popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;')
|
|
||||||
.arrow
|
|
||||||
h3.popover-title
|
|
||||||
a(target='_blank', href='http://www.kickstarter.com/profile/mattboch') Matt Boch
|
|
||||||
.popover-content
|
|
||||||
p
|
|
||||||
| Welcome to the Stable! I'm Matt, the beast master. Choose a pet here to adventure at your side - they aren't much help yet, but I forsee a time when they're able to
|
|
||||||
a(href='https://trello.com/card/mounts/50e5d3684fe3a7266b0036d6/221') grow into powerful steeds
|
|
||||||
| ! Until that day,
|
|
||||||
a(target='_blank', href='https://f.cloud.github.com/assets/2374703/164631/3ed5fa6c-78cd-11e2-8743-f65ac477b55e.png') have a look-see
|
|
||||||
| at all the pets you can collect.
|
|
||||||
h4 {{userPets.length}} / {{totalPets}} Pets Found
|
|
||||||
|
|
||||||
menu.pets(type='list')
|
|
||||||
li.customize-menu(ng-repeat='pet in pets')
|
|
||||||
menu
|
|
||||||
div(ng-repeat='potion in hatchingPotions', tooltip='{{potion.name}} {{pet.name}}')
|
|
||||||
button(class="pet-button Pet-{{pet.name}}-{{potion.name}}", ng-show='hasPet(pet.name, potion.name)', ng-class="{active: isCurrentPet(pet.name, potion.name)}", ng-click='choosePet(pet.name, potion.name)')
|
|
||||||
button(class="pet-button pet-not-owned", ng-hide='hasPet(pet.name, potion.name)')
|
|
||||||
img(src='/bower_components/habitrpg-shared/img/PixelPaw.png')
|
|
||||||
|
|
||||||
h4 Rare Pets
|
|
||||||
menu
|
|
||||||
div(ng-if='hasPet("Wolf", "Veteran")')
|
|
||||||
button(class="pet-button Pet-Wolf-Veteran", ng-class='{active: isCurrentPet("Wolf", "Veteran")}', ng-click='choosePet("Wolf", "Veteran")', tooltip='Veteran Wolf')
|
|
||||||
div(ng-if='hasPet("Wolf", "Cerberus")')
|
|
||||||
button(class="pet-button Pet-Wolf-Cerberus", ng-class='{active: isCurrentPet("Wolf", "Cerberus")}', ng-click='choosePet("Wolf", "Cerberus")', tooltip='Cerberus Pup')
|
|
||||||
@@ -1,127 +1,143 @@
|
|||||||
.row-fluid(ng-controller='UserCtrl')
|
script(id='partials/options.profile.avatar.html', type='text/ng-template')
|
||||||
|
.row-fluid
|
||||||
// ---- Customize -----
|
.span4
|
||||||
.span4.border-right
|
menu(type='list')
|
||||||
menu(type='list')
|
// body-type
|
||||||
// gender
|
|
||||||
li.customize-menu
|
|
||||||
menu(label='Head')
|
|
||||||
menu
|
|
||||||
button.m_head_0.customize-option(type='button', ng-click='set("preferences.gender","m")')
|
|
||||||
button.f_head_0.customize-option(type='button', ng-click='set("preferences.gender","f")')
|
|
||||||
label.checkbox
|
|
||||||
input(type='checkbox', ng-model='user.preferences.showHelm', ng-change='toggleHelm(user.preferences.showHelm)')
|
|
||||||
| Show Helm
|
|
||||||
hr
|
|
||||||
|
|
||||||
// hair
|
|
||||||
li.customize-menu
|
|
||||||
menu(label='Hair')
|
|
||||||
button(class='{{user.preferences.gender}}_hair_blond customize-option', type='button', ng-click='set("preferences.hair","blond")')
|
|
||||||
button(class='{{user.preferences.gender}}_hair_black customize-option', type='button', ng-click='set("preferences.hair","black")')
|
|
||||||
button(class='{{user.preferences.gender}}_hair_brown customize-option', type='button', ng-click='set("preferences.hair","brown")')
|
|
||||||
button(class='{{user.preferences.gender}}_hair_white customize-option', type='button', ng-click='set("preferences.hair","white")')
|
|
||||||
button(class='{{user.preferences.gender}}_hair_red customize-option', type='button', ng-click='set("preferences.hair","red")')
|
|
||||||
hr
|
|
||||||
|
|
||||||
// skin
|
|
||||||
li.customize-menu
|
|
||||||
menu(label='Basic Skins')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_asian', ng-click='set("preferences.skin","asian")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_white', ng-click='set("preferences.skin","white")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ea8349', ng-click='set("preferences.skin","ea8349")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_c06534', ng-click='set("preferences.skin","c06534")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_98461a', ng-click='set("preferences.skin","98461a")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_black', ng-click='set("preferences.skin","black")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_dead', ng-click='set("preferences.skin","dead")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_orc', ng-click='set("preferences.skin","orc")')
|
|
||||||
|
|
||||||
// Rainbow Skin
|
|
||||||
h5.
|
|
||||||
Rainbow Skins - 2<span class="Pet_Currency_Gem1x inline-gems"/>/skin
|
|
||||||
//menu(label='Rainbow Skins (2G / skin)')
|
|
||||||
menu
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_eb052b', ng-class='{locked: !user.purchased.skin.eb052b}', ng-click='unlock("skin.eb052b")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f69922', ng-class='{locked: !user.purchased.skin.f69922}', ng-click='unlock("skin.f69922")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f5d70f', ng-class='{locked: !user.purchased.skin.f5d70f}', ng-click='unlock("skin.f5d70f")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_0ff591', ng-class='{locked: !user.purchased.skin.0ff591}', ng-click='unlock("skin.0ff591")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_2b43f6', ng-class='{locked: !user.purchased.skin.2b43f6}', ng-click='unlock("skin.2b43f6")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_d7a9f7', ng-class='{locked: !user.purchased.skin.d7a9f7}', ng-click='unlock("skin.d7a9f7")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_800ed0', ng-class='{locked: !user.purchased.skin.800ed0}', ng-click='unlock("skin.800ed0")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_rainbow', ng-class='{locked: !user.purchased.skin.rainbow}', ng-click='unlock("skin.rainbow")')
|
|
||||||
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock(["skin.eb052b", "skin.f69922", "skin.f5d70f", "skin.0ff591", "skin.2b43f6", "skin.d7a9f7", "skin.800ed0", "skin.rainbow"])') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
|
|
||||||
|
|
||||||
// Special Events
|
|
||||||
div.well.limited-edition
|
|
||||||
.label.label-info.pull-right(popover='Available for purchase until November 10th (but permanently in your options if purchased).', popover-title='Limited Edition', popover-placement='right', popover-trigger='mouseenter')
|
|
||||||
| Limited Edition
|
|
||||||
i.icon.icon-question-sign
|
|
||||||
h5.
|
|
||||||
Spooky Skins - 2<span class="Pet_Currency_Gem1x inline-gems"/>/skin
|
|
||||||
//menu(label='Spooky Skins (2 Gems / skin)')
|
|
||||||
menu
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_monster', ng-class='{locked: !user.purchased.skin.monster}', ng-click='unlock("skin.monster")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_pumpkin', ng-class='{locked: !user.purchased.skin.pumpkin}', ng-click='unlock("skin.pumpkin")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_skeleton', ng-class='{locked: !user.purchased.skin.skeleton}', ng-click='unlock("skin.skeleton")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_zombie', ng-class='{locked: !user.purchased.skin.zombie}', ng-click='unlock("skin.zombie")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ghost', ng-class='{locked: !user.purchased.skin.ghost}', ng-click='unlock("skin.ghost")')
|
|
||||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_shadow', ng-class='{locked: !user.purchased.skin.shadow}', ng-click='unlock("skin.shadow")')
|
|
||||||
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.monster && user.purchased.skin.pumpkin && user.purchased.skin.skeleton && user.purchased.skin.zombie && user.purchased.skin.ghost && user.purchased.skin.shadow', ng-click='unlock(["skin.monster", "skin.pumpkin", "skin.skeleton", "skin.zombie", "skin.ghost", "skin.shadow"])') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
|
|
||||||
|
|
||||||
|
|
||||||
menu(ng-show='user.preferences.gender=="f"', type='list')
|
|
||||||
li.customize-menu
|
li.customize-menu
|
||||||
menu(label='Clothing')
|
menu(label='Head')
|
||||||
button.f_armor_0_v1.customize-option(type='button', ng-click='set("preferences.armorSet","v1")')
|
menu
|
||||||
button.f_armor_0_v2.customize-option(type='button', ng-click='set("preferences.armorSet","v2")')
|
button.m_head_0.customize-option(type='button', ng-click='set("preferences.gender","m")')
|
||||||
|
button.f_head_0.customize-option(type='button', ng-click='set("preferences.gender","f")')
|
||||||
|
label.checkbox
|
||||||
|
input(type='checkbox', ng-model='user.preferences.showHelm', ng-change='toggleHelm(user.preferences.showHelm)')
|
||||||
|
| Show Helm
|
||||||
|
|
||||||
// ------ Stats ------
|
menu(ng-show='user.preferences.gender=="f"', type='list')
|
||||||
.span4.border-right
|
li.customize-menu
|
||||||
include ../shared/profiles/stats
|
menu(label='Clothing')
|
||||||
|
button.f_armor_0_v1.customize-option(type='button', ng-click='set("preferences.armorSet","v1")')
|
||||||
|
button.f_armor_0_v2.customize-option(type='button', ng-click='set("preferences.armorSet","v2")')
|
||||||
|
|
||||||
// ------- Edit -------
|
.span4
|
||||||
.span4
|
// hair
|
||||||
button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile') Edit
|
li.customize-menu
|
||||||
button.btn.btn-primary(ng-click='save()', ng-show='_editing.profile') Save
|
menu(label='Hair')
|
||||||
div(ng-show='!_editing.profile')
|
button(class='{{user.preferences.gender}}_hair_blond customize-option', type='button', ng-click='set("preferences.hair","blond")')
|
||||||
h4 Display Name
|
button(class='{{user.preferences.gender}}_hair_black customize-option', type='button', ng-click='set("preferences.hair","black")')
|
||||||
span(ng-show='profile.profile.name') {{profile.profile.name}}
|
button(class='{{user.preferences.gender}}_hair_brown customize-option', type='button', ng-click='set("preferences.hair","brown")')
|
||||||
span.muted(ng-hide='profile.profile.name') - None -
|
button(class='{{user.preferences.gender}}_hair_white customize-option', type='button', ng-click='set("preferences.hair","white")')
|
||||||
|
button(class='{{user.preferences.gender}}_hair_red customize-option', type='button', ng-click='set("preferences.hair","red")')
|
||||||
|
|
||||||
h4 Photo
|
.span4
|
||||||
img(ng-show='profile.profile.imageUrl', ng-src='{{profile.profile.imageUrl}}')
|
// skin
|
||||||
span.muted(ng-hide='profile.profile.imageUrl') - None -
|
li.customize-menu
|
||||||
|
menu(label='Basic Skins')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_asian', ng-click='set("preferences.skin","asian")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_white', ng-click='set("preferences.skin","white")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ea8349', ng-click='set("preferences.skin","ea8349")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_c06534', ng-click='set("preferences.skin","c06534")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_98461a', ng-click='set("preferences.skin","98461a")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_black', ng-click='set("preferences.skin","black")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_dead', ng-click='set("preferences.skin","dead")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_orc', ng-click='set("preferences.skin","orc")')
|
||||||
|
|
||||||
h4 Blurb
|
// Rainbow Skin
|
||||||
markdown(ng-show='profile.profile.blurb', ng-model='profile.profile.blurb')
|
h5.
|
||||||
span.muted(ng-hide='profile.profile.blurb') - None -
|
Rainbow Skins - 2<span class="Pet_Currency_Gem1x inline-gems"/>/skin
|
||||||
//{{profile.profile.blurb | linky:'_blank'}}
|
//menu(label='Rainbow Skins (2G / skin)')
|
||||||
|
menu
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_eb052b', ng-class='{locked: !user.purchased.skin.eb052b}', ng-click='unlock("skin.eb052b")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f69922', ng-class='{locked: !user.purchased.skin.f69922}', ng-click='unlock("skin.f69922")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f5d70f', ng-class='{locked: !user.purchased.skin.f5d70f}', ng-click='unlock("skin.f5d70f")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_0ff591', ng-class='{locked: !user.purchased.skin.0ff591}', ng-click='unlock("skin.0ff591")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_2b43f6', ng-class='{locked: !user.purchased.skin.2b43f6}', ng-click='unlock("skin.2b43f6")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_d7a9f7', ng-class='{locked: !user.purchased.skin.d7a9f7}', ng-click='unlock("skin.d7a9f7")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_800ed0', ng-class='{locked: !user.purchased.skin.800ed0}', ng-click='unlock("skin.800ed0")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_rainbow', ng-class='{locked: !user.purchased.skin.rainbow}', ng-click='unlock("skin.rainbow")')
|
||||||
|
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock(["skin.eb052b", "skin.f69922", "skin.f5d70f", "skin.0ff591", "skin.2b43f6", "skin.d7a9f7", "skin.800ed0", "skin.rainbow"])') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
|
||||||
|
|
||||||
h4 Websites
|
// Special Events
|
||||||
ul(ng-show='profile.profile.websites.length > 0')
|
div.well.limited-edition
|
||||||
// TODO let's remove links eventually, since we can do markdown on profiles
|
.label.label-info.pull-right(popover='Available for purchase until November 10th (but permanently in your options if purchased).', popover-title='Limited Edition', popover-placement='right', popover-trigger='mouseenter')
|
||||||
li(ng-repeat='website in profile.profile.websites')
|
| Limited Edition
|
||||||
a(target='_blank', ng-href='{{website}}') {{website}}
|
i.icon.icon-question-sign
|
||||||
span.muted(ng-hide='profile.profile.websites.length > 0') - None -
|
h5.
|
||||||
|
Spooky Skins - 2<span class="Pet_Currency_Gem1x inline-gems"/>/skin
|
||||||
|
//menu(label='Spooky Skins (2 Gems / skin)')
|
||||||
|
menu
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_monster', ng-class='{locked: !user.purchased.skin.monster}', ng-click='unlock("skin.monster")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_pumpkin', ng-class='{locked: !user.purchased.skin.pumpkin}', ng-click='unlock("skin.pumpkin")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_skeleton', ng-class='{locked: !user.purchased.skin.skeleton}', ng-click='unlock("skin.skeleton")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_zombie', ng-class='{locked: !user.purchased.skin.zombie}', ng-click='unlock("skin.zombie")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ghost', ng-class='{locked: !user.purchased.skin.ghost}', ng-click='unlock("skin.ghost")')
|
||||||
|
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_shadow', ng-class='{locked: !user.purchased.skin.shadow}', ng-click='unlock("skin.shadow")')
|
||||||
|
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.monster && user.purchased.skin.pumpkin && user.purchased.skin.skeleton && user.purchased.skin.zombie && user.purchased.skin.ghost && user.purchased.skin.shadow', ng-click='unlock(["skin.monster", "skin.pumpkin", "skin.skeleton", "skin.zombie", "skin.ghost", "skin.shadow"])') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
|
||||||
|
|
||||||
div.whatever-options(ng-show='_editing.profile')
|
script(id='partials/options.profile.stats.html', type='text/ng-template')
|
||||||
// TODO use photo-upload instead: https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/xMmADvxBOak
|
.row-fluid
|
||||||
.control-group.option-large
|
.span6.border-right
|
||||||
label.control-label Display Name
|
include ../shared/profiles/stats
|
||||||
input.option-content(type='text', placeholder='Full Name', ng-model='editingProfile.name')
|
.span6.border-right
|
||||||
.control-group.option-large
|
include ../shared/profiles/achievements
|
||||||
label.control-label Photo Url
|
|
||||||
input.option-content(type='url', ng-model='editingProfile.imageUrl', placeholder='Image Url')
|
script(id='partials/options.profile.profile.html', type='text/ng-template')
|
||||||
.control-group.option-large
|
button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile') Edit
|
||||||
label.control-label Blurb
|
button.btn.btn-primary(ng-click='save()', ng-show='_editing.profile') Save
|
||||||
textarea.option-content(style='height:15em;', placeholder='Blurb', ng-model='editingProfile.blurb')
|
div(ng-show='!_editing.profile')
|
||||||
include ../shared/formatting-help
|
h4 Display Name
|
||||||
.control-group.option-large
|
span(ng-show='profile.profile.name') {{profile.profile.name}}
|
||||||
label.control-label Websites
|
span.muted(ng-hide='profile.profile.name') - None -
|
||||||
form(ng-submit='addWebsite()')
|
|
||||||
input.option-content(type='url', ng-model='_newWebsite', placeholder='Add Website')
|
h4 Photo
|
||||||
ul
|
img(ng-show='profile.profile.imageUrl', ng-src='{{profile.profile.imageUrl}}')
|
||||||
li(ng-repeat='website in editingProfile.websites')
|
span.muted(ng-hide='profile.profile.imageUrl') - None -
|
||||||
| {{website}}
|
|
||||||
a(ng-click='removeWebsite($index)')
|
h4 Blurb
|
||||||
i.icon-remove
|
markdown(ng-show='profile.profile.blurb', ng-model='profile.profile.blurb')
|
||||||
|
span.muted(ng-hide='profile.profile.blurb') - None -
|
||||||
|
//{{profile.profile.blurb | linky:'_blank'}}
|
||||||
|
|
||||||
|
h4 Websites
|
||||||
|
ul(ng-show='profile.profile.websites.length > 0')
|
||||||
|
// TODO let's remove links eventually, since we can do markdown on profiles
|
||||||
|
li(ng-repeat='website in profile.profile.websites')
|
||||||
|
a(target='_blank', ng-href='{{website}}') {{website}}
|
||||||
|
span.muted(ng-hide='profile.profile.websites.length > 0') - None -
|
||||||
|
|
||||||
|
div.whatever-options(ng-show='_editing.profile')
|
||||||
|
// TODO use photo-upload instead: https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/xMmADvxBOak
|
||||||
|
.control-group.option-large
|
||||||
|
label.control-label Display Name
|
||||||
|
input.option-content(type='text', placeholder='Full Name', ng-model='editingProfile.name')
|
||||||
|
.control-group.option-large
|
||||||
|
label.control-label Photo Url
|
||||||
|
input.option-content(type='url', ng-model='editingProfile.imageUrl', placeholder='Image Url')
|
||||||
|
.control-group.option-large
|
||||||
|
label.control-label Blurb
|
||||||
|
textarea.option-content(style='height:15em;', placeholder='Blurb', ng-model='editingProfile.blurb')
|
||||||
|
include ../shared/formatting-help
|
||||||
|
.control-group.option-large
|
||||||
|
label.control-label Websites
|
||||||
|
form(ng-submit='addWebsite()')
|
||||||
|
input.option-content(type='url', ng-model='_newWebsite', placeholder='Add Website')
|
||||||
|
ul
|
||||||
|
li(ng-repeat='website in editingProfile.websites')
|
||||||
|
| {{website}}
|
||||||
|
a(ng-click='removeWebsite($index)')
|
||||||
|
i.icon-remove
|
||||||
|
|
||||||
|
script(id='partials/options.profile.html', type="text/ng-template")
|
||||||
|
ul.nav.nav-tabs
|
||||||
|
li(ng-class="{ active: $state.includes('options.profile.avatar') }")
|
||||||
|
a(ui-sref='options.profile.avatar')
|
||||||
|
| Avatar
|
||||||
|
li(ng-class="{ active: $state.includes('options.profile.stats') }")
|
||||||
|
a(ui-sref='options.profile.stats')
|
||||||
|
| Stats & Achievements
|
||||||
|
li(ng-class="{ active: $state.includes('options.profile.profile') }")
|
||||||
|
a(ui-sref='options.profile.profile')
|
||||||
|
| Profile
|
||||||
|
|
||||||
|
.tab-content
|
||||||
|
.tab-pane.active
|
||||||
|
div(ui-view)
|
||||||
@@ -1,43 +1,44 @@
|
|||||||
.row-fluid(ng-controller='SettingsCtrl')
|
script(type='text/ng-template', id='partials/options.settings.html')
|
||||||
.personal-options.span6.border-right
|
.row-fluid
|
||||||
h2 Settings
|
.personal-options.span6.border-right
|
||||||
h4 Custom Day Start
|
h2 Settings
|
||||||
.option-group.option-short
|
h4 Custom Day Start
|
||||||
input.option-content.option-time(type='number', min='0', max='24', ng-model='user.preferences.dayStart', ng-change='saveDayStart()')
|
.option-group.option-short
|
||||||
span.input-suffix :00 (24h clock)
|
input.option-content.option-time(type='number', min='0', max='24', ng-model='user.preferences.dayStart', ng-change='saveDayStart()')
|
||||||
div
|
span.input-suffix :00 (24h clock)
|
||||||
small
|
div
|
||||||
| Habit defaults to check and reset your dailies at midnight in your time zone each day. You can customize the hour here (enter a number between 0 and 24).
|
small
|
||||||
hr
|
| Habit defaults to check and reset your dailies at midnight in your time zone each day. You can customize the hour here (enter a number between 0 and 24).
|
||||||
h4 Misc
|
|
||||||
button.btn(ng-hide='user.preferences.hideHeader', ng-click='set("preferences.hideHeader",true)') Hide Header
|
|
||||||
button.btn(ng-show='user.preferences.hideHeader', ng-click='set("preferences.hideHeader",false)') Show Header
|
|
||||||
button.btn(ng-click='showTour()') Show Tour
|
|
||||||
button.btn(ng-click='showBailey()') Show Bailey
|
|
||||||
|
|
||||||
div(ng-show='user.auth.local')
|
|
||||||
hr
|
hr
|
||||||
h4 Change Password
|
h4 Misc
|
||||||
form(ng-submit='changePassword(changePass)', ng-show='user.auth.local')
|
button.btn(ng-hide='user.preferences.hideHeader', ng-click='set("preferences.hideHeader",true)') Hide Header
|
||||||
.control-group
|
button.btn(ng-show='user.preferences.hideHeader', ng-click='set("preferences.hideHeader",false)') Show Header
|
||||||
input(type='password', placeholder='Old Password', ng-model='changePass.oldPassword', required)
|
button.btn(ng-click='showTour()') Show Tour
|
||||||
.control-group
|
button.btn(ng-click='showBailey()') Show Bailey
|
||||||
input(type='password', placeholder='New Password', ng-model='changePass.newPassword', required)
|
|
||||||
.control-group
|
|
||||||
input(type='password', placeholder='Confirm New Password', ng-model='changePass.confirmNewPassword', required)
|
|
||||||
input.btn(type='submit', value='Submit')
|
|
||||||
|
|
||||||
hr
|
div(ng-show='user.auth.local')
|
||||||
h4 Danger Zone
|
hr
|
||||||
a.btn.btn-danger(ng-click='modals.reset = true', tooltip='Resets your entire account (dangerous).') Reset
|
h4 Change Password
|
||||||
a.btn.btn-danger(ng-click='modals.restore = true', tooltip='Restores attributes to your character.') Restore
|
form(ng-submit='changePassword(changePass)', ng-show='user.auth.local')
|
||||||
a.btn.btn-danger(ng-click='modals.delete = true', tooltip='Delete your account.') Delete
|
.control-group
|
||||||
.span6
|
input(type='password', placeholder='Old Password', ng-model='changePass.oldPassword', required)
|
||||||
h2 API
|
.control-group
|
||||||
small Copy these for use in third party applications.
|
input(type='password', placeholder='New Password', ng-model='changePass.newPassword', required)
|
||||||
h6 User ID
|
.control-group
|
||||||
pre.prettyprint {{user.id}}
|
input(type='password', placeholder='Confirm New Password', ng-model='changePass.confirmNewPassword', required)
|
||||||
h6 API Token
|
input.btn(type='submit', value='Submit')
|
||||||
pre.prettyprint {{user.apiToken}}
|
|
||||||
h6 QR Code
|
hr
|
||||||
img(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7Baddress%3A%22https%3A%2F%2Fhabitrpg.com%22%2Cuser%3A%22{{user.id}}%22%2Ckey%3A%22{{user.apiToken}}%22%7D,&choe=UTF-8&chld=L', alt='qrcode')
|
h4 Danger Zone
|
||||||
|
a.btn.btn-danger(ng-click='modals.reset = true', tooltip='Resets your entire account (dangerous).') Reset
|
||||||
|
a.btn.btn-danger(ng-click='modals.restore = true', tooltip='Restores attributes to your character.') Restore
|
||||||
|
a.btn.btn-danger(ng-click='modals.delete = true', tooltip='Delete your account.') Delete
|
||||||
|
.span6
|
||||||
|
h2 API
|
||||||
|
small Copy these for use in third party applications.
|
||||||
|
h6 User ID
|
||||||
|
pre.prettyprint {{user.id}}
|
||||||
|
h6 API Token
|
||||||
|
pre.prettyprint {{user.apiToken}}
|
||||||
|
h6 QR Code
|
||||||
|
img(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7Baddress%3A%22https%3A%2F%2Fhabitrpg.com%22%2Cuser%3A%22{{user.id}}%22%2Ckey%3A%22{{user.apiToken}}%22%7D,&choe=UTF-8&chld=L', alt='qrcode')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
figure.herobox(ng-click='clickMember(profile._id)', data-name='{{username(profile.auth, profile.profile.name)}}', ng-class='{isUser: profile.id==user.id, hasPet: profile.items.pet}', data-level='{{profile.stats.lvl}}', data-uid='{{profile.id}}', rel='popover', data-placement='bottom', data-trigger='hover', data-html='true', data-content="<div ng-hide='profile.id == user.id'> <div class='progress progress-danger' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.hp, 50)}}%;'></div> </div> <div class='progress progress-warning' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.exp, tnl(profile.stats.lvl))}}%;'></div> </div> <div>Level: {{profile.stats.lvl}}</div> <div>GP: {{profile.stats.gp | number:0}}</div> <div>{{count(profile.items.pets)}} / 90 Pets Found</div> </div>")
|
figure.herobox(ng-click='clickMember(profile._id)', data-name='{{profile.profile.name}}', ng-class='{isUser: profile.id==user.id, hasPet: profile.items.pet}', data-level='{{profile.stats.lvl}}', data-uid='{{profile.id}}', rel='popover', data-placement='bottom', data-trigger='hover', data-html='true', data-content="<div ng-hide='profile.id == user.id'> <div class='progress progress-danger' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.hp, 50)}}%;'></div> </div> <div class='progress progress-warning' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.exp, tnl(profile.stats.lvl))}}%;'></div> </div> <div>Level: {{profile.stats.lvl}}</div> <div>GP: {{profile.stats.gp | number:0}}</div> <div>{{count(profile.items.pets)}} / 90 Pets Found</div> </div>")
|
||||||
.character-sprites
|
.character-sprites
|
||||||
span(ng-class='{zzz:profile.flags.rest}')
|
span(ng-class='{zzz:profile.flags.rest}')
|
||||||
span(class='{{profile.preferences.gender}}_skin_{{profile.preferences.skin}}')
|
span(class='{{profile.preferences.gender}}_skin_{{profile.preferences.skin}}')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
button.task-action-btn.tile.solid(ng-hide='authenticated()', style='cursor: pointer;', ng-click="modals.login = true") Login / Register
|
button.task-action-btn.tile.solid(ng-hide='authenticated()', style='cursor: pointer;', ng-click="modals.login = true") Login / Register
|
||||||
ul.nav.site-nav(ng-show='authenticated()')
|
ul.nav.site-nav(ng-show='authenticated()')
|
||||||
li.flyout(ng-controller='MenuCtrl')
|
li.flyout(ng-controller='MenuCtrl')
|
||||||
h1.task-action-btn.tile.solid.user-reporter {{username(user.auth, user.profile.name)}}
|
h1.task-action-btn.tile.solid.user-reporter {{user.profile.name}}
|
||||||
ul.flyout-content.nav.stacked
|
ul.flyout-content.nav.stacked
|
||||||
li
|
li
|
||||||
a.task-action-btn.tile.solid
|
a.task-action-btn.tile.solid
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ div(ng-controller='MemberModalCtrl')
|
|||||||
#memberModal(modal='modals.member')
|
#memberModal(modal='modals.member')
|
||||||
.modal-header
|
.modal-header
|
||||||
h3
|
h3
|
||||||
span {{username(profile.auth, profile.profile.name)}}
|
span {{profile.profile.name}}
|
||||||
span(ng-show='profile.backer.contributor') - {{profile.backer.contributor}}
|
span(ng-show='profile.backer.contributor') - {{profile.backer.contributor}}
|
||||||
.modal-body
|
.modal-body
|
||||||
.row-fluid
|
.row-fluid
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
div(ng-controller='TasksCtrl')
|
// Note here, we need this part of Habit to be a directive since we're going to be passing it variables from various
|
||||||
include ./filters
|
// parts of the app. The alternative would be to create new scopes for different containing sections, but that
|
||||||
|
// started to get unwieldy
|
||||||
|
script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
||||||
.grid
|
.grid
|
||||||
.module(ng-controller='TasksCtrl', ng-repeat='list in taskLists', ng-class='{"rewards-module": list.type==="reward"}')
|
.module(ng-repeat='list in lists', ng-class='{"rewards-module": list.type==="reward"}')
|
||||||
.task-column(class='{{list.type}}s')
|
.task-column(class='{{list.type}}s')
|
||||||
|
|
||||||
// Todos export/graph options
|
// Todos export/graph options
|
||||||
span.option-box.pull-right(ng-if='list.main && list.type=="todo"')
|
span.option-box.pull-right(ng-if='main && list.type=="todo"')
|
||||||
a.option-action(ng-show='user.history.todos', ng-click='toggleChart("todos")', tooltip='Progress')
|
a.option-action(ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip='Progress')
|
||||||
i.icon-signal
|
i.icon-signal
|
||||||
//-a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
|
//-a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
|
||||||
a.option-action(ng-click='notPorted()', tooltip='iCal', ng-show='false')
|
a.option-action(ng-click='notPorted()', tooltip='iCal', ng-show='false')
|
||||||
@@ -14,7 +16,7 @@ div(ng-controller='TasksCtrl')
|
|||||||
// <a href="https://www.google.com/calendar/render?cid={{encodeiCalLink(_user.id, _user.apiToken)}}" rel=tooltip title="Google Calendar"><i class=icon-calendar></i></a>
|
// <a href="https://www.google.com/calendar/render?cid={{encodeiCalLink(_user.id, _user.apiToken)}}" rel=tooltip title="Google Calendar"><i class=icon-calendar></i></a>
|
||||||
|
|
||||||
// Gold & Gems
|
// Gold & Gems
|
||||||
span.option-box.pull-right.wallet(ng-if='list.main && list.type=="reward"')
|
span.option-box.pull-right.wallet(ng-if='main && list.type=="reward"')
|
||||||
.money
|
.money
|
||||||
| {{gold(user.stats.gp)}}
|
| {{gold(user.stats.gp)}}
|
||||||
span.shop_gold(tooltip='Gold')
|
span.shop_gold(tooltip='Gold')
|
||||||
@@ -29,18 +31,18 @@ div(ng-controller='TasksCtrl')
|
|||||||
.todos-chart(ng-if='list.type == "todo"', ng-show='charts.todos')
|
.todos-chart(ng-if='list.type == "todo"', ng-show='charts.todos')
|
||||||
|
|
||||||
// Add New
|
// Add New
|
||||||
form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-show='list.editable', ng-hide='list.showCompleted && list.type=="todo"', data-task-type='{{list.type}}', ng-submit='addTask(list)')
|
form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-hide='obj._locked || (list.showCompleted && list.type=="todo")', ng-submit='addTask(list)')
|
||||||
span.addtask-field
|
span.addtask-field
|
||||||
input(type='text', ng-model='list.newTask', placeholder='{{list.placeHolder}}', required)
|
input(type='text', ng-model='list.newTask', placeholder='{{list.placeHolder}}', required)
|
||||||
input.addtask-btn(type='submit', value='+', ng-disabled='new{{list.type}}form.$invalid')
|
input.addtask-btn(type='submit', value='+', ng-disabled='new{{list.type}}form.$invalid')
|
||||||
hr
|
hr
|
||||||
|
|
||||||
// Actual List
|
// Actual List
|
||||||
ul(class='{{list.type}}s', ng-show='user[list.type + "s"].length > 0', habitrpg-sortable)
|
ul(class='{{list.type}}s', ng-show='obj[list.type + "s"].length > 0', habitrpg-sortable)
|
||||||
include ./task
|
include ./task
|
||||||
|
|
||||||
// Static Rewards
|
// Static Rewards
|
||||||
ul.items(ng-show='list.main && list.type=="reward" && user.flags.itemsEnabled')
|
ul.items(ng-show='main && list.type=="reward" && user.flags.itemsEnabled')
|
||||||
li.task.reward-item(ng-hide='item.hide', ng-repeat='item in itemStore')
|
li.task.reward-item(ng-hide='item.hide', ng-repeat='item in itemStore')
|
||||||
// right-hand side control buttons
|
// right-hand side control buttons
|
||||||
.task-meta-controls
|
.task-meta-controls
|
||||||
@@ -59,10 +61,16 @@ div(ng-controller='TasksCtrl')
|
|||||||
|
|
||||||
br
|
br
|
||||||
|
|
||||||
include ./ads
|
// Ads
|
||||||
|
div(ng-if='main && !user.purchased.ads && list.type!="reward"')
|
||||||
|
span.pull-right
|
||||||
|
a(ng-click='modals.buyGems=true', tooltip='Remove Ads')
|
||||||
|
i.icon-remove
|
||||||
|
// Habit3
|
||||||
|
ins.adsbygoogle(ng-init='initAds()', style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576')
|
||||||
|
|
||||||
// Todo Tabs
|
// Todo Tabs
|
||||||
div(ng-if='list.type=="todo"', ng-class='{"tabbable tabs-below": list.type=="todo"}')
|
div(ng-if='main && list.type=="todo"', ng-class='{"tabbable tabs-below": list.type=="todo"}')
|
||||||
button.task-action-btn.tile.spacious.bright(ng-show='list.showCompleted', ng-click='clearCompleted()') Clear Completed
|
button.task-action-btn.tile.spacious.bright(ng-show='list.showCompleted', ng-click='clearCompleted()') Clear Completed
|
||||||
// remaining/completed tabs
|
// remaining/completed tabs
|
||||||
ul.nav.nav-tabs
|
ul.nav.nav-tabs
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,user.filters,user.preferences.dayStart,user.lastCron,list.showCompleted,list.main)}}', data-id='{{task.id}}')
|
li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', data-id='{{task.id}}')
|
||||||
// right-hand side control buttons
|
// right-hand side control buttons
|
||||||
.task-meta-controls
|
.task-meta-controls
|
||||||
|
|
||||||
// Due Date
|
// Due Date
|
||||||
span.task-date(ng-if='task.type=="todo" && task.date')
|
span.task-date(ng-if='task.type=="todo" && task.date')
|
||||||
| {{task.date}}
|
| {{task.date}}
|
||||||
@@ -9,28 +10,27 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||||||
span(tooltip='Streak Counter')
|
span(tooltip='Streak Counter')
|
||||||
i.icon-forward
|
i.icon-forward
|
||||||
|
|
||||||
i.icon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='noTags(task.tags)')
|
// Icons only available if you own the tasks (aka, hidden from challenge stats)
|
||||||
|
span(ng-if='!obj._locked')
|
||||||
|
i.icon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='noTags(task.tags)')
|
||||||
|
// edit
|
||||||
|
a(ng-hide='task._editing', ng-click='editTask(task)', tooltip='Edit')
|
||||||
|
i.icon-pencil(ng-hide='task._editing')
|
||||||
|
// cancel
|
||||||
|
a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip='Cancel')
|
||||||
|
i.icon-remove(ng-hide='!task._editing')
|
||||||
|
//challenges
|
||||||
|
span(ng-if='task.challenge.id')
|
||||||
|
span(ng-if='task.challenge.broken')
|
||||||
|
i.icon-bullhorn(style='background-color:red;', ng-click='task._edit=true', tooltip="Broken Challenge Link")
|
||||||
|
span(ng-if='!task.challenge.broken')
|
||||||
|
i.icon-bullhorn(tooltip="Challenge Task")
|
||||||
|
// delete
|
||||||
|
a(ng-if='!task.challenge.id', ng-click='removeTask(list.tasks, $index)', tooltip='Delete')
|
||||||
|
i.icon-trash
|
||||||
|
|
||||||
// edit
|
|
||||||
a(ng-hide='task._editing', ng-click='toggleEdit(task)', tooltip='Edit')
|
|
||||||
i.icon-pencil(ng-hide='task._editing')
|
|
||||||
// cancel
|
|
||||||
a(ng-hide='!task._editing', ng-click='toggleEdit(task)', tooltip='Cancel')
|
|
||||||
i.icon-remove(ng-hide='!task._editing')
|
|
||||||
//- challenges
|
|
||||||
// {{#if task.challenge}}
|
|
||||||
// {{#if brokenChallengeLink(task)}}
|
|
||||||
// <i class='icon-bullhorn' style='background-color:red;' x-bind=click:toggleTaskEdit tooltip="Broken Challenge Link"></i>
|
|
||||||
// {{else}}
|
|
||||||
// <i class='icon-bullhorn' tooltip="Challenge Task"></i>
|
|
||||||
// {{/}}
|
|
||||||
// {{else}}
|
|
||||||
|
|
||||||
// delete
|
|
||||||
a(ng-click='remove(task)', tooltip='Delete')
|
|
||||||
i.icon-trash
|
|
||||||
// chart
|
// chart
|
||||||
a(ng-show='task.history', ng-click='toggleChart(task.id, task)', tooltip='Progress')
|
a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip='Progress')
|
||||||
i.icon-signal
|
i.icon-signal
|
||||||
// notes
|
// notes
|
||||||
span.task-notes(ng-show='task.notes && !task._editing', popover-trigger='mouseenter', popover-placement='left', popover='{{task.notes}}', popover-title='{{task.text}}')
|
span.task-notes(ng-show='task.notes && !task._editing', popover-trigger='mouseenter', popover-placement='left', popover='{{task.notes}}', popover-title='{{task.text}}')
|
||||||
@@ -40,66 +40,59 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||||||
.task-controls.task-primary
|
.task-controls.task-primary
|
||||||
|
|
||||||
// Habits
|
// Habits
|
||||||
span(ng-if='list.main && task.type=="habit"')
|
span(ng-if='task.type=="habit"')
|
||||||
// only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges
|
|
||||||
a.task-action-btn(ng-if='task.up', ng-click='score(task,"up")') +
|
a.task-action-btn(ng-if='task.up', ng-click='score(task,"up")') +
|
||||||
a.task-action-btn(ng-if='task.down', ng-click='score(task,"down")') -
|
a.task-action-btn(ng-if='task.down', ng-click='score(task,"down")') -
|
||||||
//span(ng-if='!list.main')
|
|
||||||
// span.task-action-btn(ng-show='task.up') +
|
|
||||||
// span.task-action-btn(ng-show='task.down') =
|
|
||||||
|
|
||||||
// Rewards
|
// Rewards
|
||||||
span(ng-show='list.main && task.type=="reward"')
|
span(ng-show='task.type=="reward"')
|
||||||
// only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges
|
|
||||||
a.money.btn-buy(ng-click='score(task, "down")')
|
a.money.btn-buy(ng-click='score(task, "down")')
|
||||||
span.reward-cost {{task.value}}
|
span.reward-cost {{task.value}}
|
||||||
span.shop_gold
|
span.shop_gold
|
||||||
//span(ng-if='!list.main')
|
|
||||||
// span.money.btn-buy
|
|
||||||
// span.reward-cost {{task.value}}
|
|
||||||
// span.shop_gold
|
|
||||||
|
|
||||||
// Daily & Todos
|
// Daily & Todos
|
||||||
span.task-checker.action-yesno(ng-if='task.type=="daily" || task.type=="todo"')
|
span.task-checker.action-yesno(ng-if='task.type=="daily" || task.type=="todo"')
|
||||||
// only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges
|
|
||||||
//span(ng-if='list.main')
|
|
||||||
input.visuallyhidden.focusable(id='box-{{task.id}}', type='checkbox', ng-model='task.completed', ng-change='changeCheck(task)')
|
input.visuallyhidden.focusable(id='box-{{task.id}}', type='checkbox', ng-model='task.completed', ng-change='changeCheck(task)')
|
||||||
label(for='box-{{task.id}}')
|
label(for='box-{{task.id}}')
|
||||||
//span(ng-if='!list.main')
|
|
||||||
// input.visuallyhidden.focusable(id='box-{{task.id}}-static',type='checkbox', checked='false')
|
|
||||||
// label(for='box-{{task.id}}-static')
|
|
||||||
// main content
|
// main content
|
||||||
p.task-text
|
p.task-text
|
||||||
// {{#if taskInChallenge(task)}}
|
|
||||||
// {{taskAttrFromChallenge(task,'text')}}
|
|
||||||
// {{else}}
|
|
||||||
| {{task.text}}
|
| {{task.text}}
|
||||||
// {{/}}
|
|
||||||
|
|
||||||
// edit/options dialog
|
// edit/options dialog
|
||||||
.task-options(ng-show='task._editing')
|
.task-options(ng-show='task._editing')
|
||||||
// {{#if brokenChallengeLink(task)}}
|
|
||||||
// <div class='well'>
|
// Broken Challenge
|
||||||
// <p>Broken Challenge Link: this task was part of a challenge, but (a) challenge (or containing group) has been deleted, or (b) the task was deleted from the challenge.</p>
|
.well(ng-if='task.challenge.broken')
|
||||||
// <p><a>Keep</a> | <a>Keep all from challenge</a> | <a>Delete</a> | <a>Delete all from challenge</a></p>
|
div(ng-if='task.challenge.broken=="TASK_DELETED"')
|
||||||
// </div>
|
p Broken Challenge Link: this task was part of a challenge, but has been removed from it. What would you like to do?
|
||||||
// {{/}}
|
p
|
||||||
form(ng-controller="TaskDetailsCtrl", ng-submit='save(task)')
|
a(ng-click='unlink(task, "keep")') Keep It
|
||||||
|
| |
|
||||||
|
a(ng-click="remove(list, $index)") Remove It
|
||||||
|
div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"')
|
||||||
|
p Broken Challenge Link: this task was part of a challenge, but the challenge (or group) has been deleted. What to do with the orphan tasks?
|
||||||
|
p
|
||||||
|
a(ng-click='unlink(task, "keep-all")') Keep Them
|
||||||
|
| |
|
||||||
|
a(ng-click='unlink(task, "remove-all")') Remove Them
|
||||||
|
//-div(ng-if='task.challenge.broken=="UNSUBSCRIBED"')
|
||||||
|
p Broken Challenge Link: this task was part of a challenge, but you have unsubscribed from the challenge. What to do with the orphan tasks?
|
||||||
|
p
|
||||||
|
a(ng-click="unlink(task, 'keep-all')") Keep Them
|
||||||
|
| |
|
||||||
|
a(ng-click="unlink(task, 'remove-all')") Remove Them
|
||||||
|
|
||||||
|
form(ng-submit='saveTask(task)')
|
||||||
// text & notes
|
// text & notes
|
||||||
fieldset.option-group
|
fieldset.option-group
|
||||||
// {{#unless taskInChallenge(task)}}
|
|
||||||
label.option-title Text
|
label.option-title Text
|
||||||
input.option-content(type='text', ng-model='task.text', required)
|
input.option-content(type='text', ng-model='task.text', required, ng-disabled='task.challenge.id')
|
||||||
// {{/}}
|
|
||||||
label.option-title Extra Notes
|
label.option-title Extra Notes
|
||||||
// {{#if taskInChallenge(task)}}
|
textarea.option-content(rows='3', ng-model='task.notes', ng-disabled='task.challenge.id')
|
||||||
// <textarea class="option-content" rows=3 disabled>{{taskAttrFromChallenge(task,'notes')}}</textarea>
|
|
||||||
// {{else}}
|
|
||||||
textarea.option-content(rows='3', ng-model='task.notes')
|
|
||||||
// {{/}}
|
|
||||||
// if Habit, plus/minus command options
|
// if Habit, plus/minus command options
|
||||||
// {{#unless taskInChallenge(task)}}
|
fieldset.option-group(ng-if='task.type=="habit" && !task.challenge.id')
|
||||||
fieldset.option-group(ng-if='task.type=="habit"')
|
|
||||||
legend.option-title Direction/Actions
|
legend.option-title Direction/Actions
|
||||||
span.task-checker.action-plusminus.select-toggle
|
span.task-checker.action-plusminus.select-toggle
|
||||||
input.visuallyhidden.focusable(id='{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
|
input.visuallyhidden.focusable(id='{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
|
||||||
@@ -107,57 +100,54 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||||||
span.task-checker.action-plusminus.select-toggle
|
span.task-checker.action-plusminus.select-toggle
|
||||||
input.visuallyhidden.focusable(id='{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
|
input.visuallyhidden.focusable(id='{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
|
||||||
label(for='{{task.id}}-option-minus')
|
label(for='{{task.id}}-option-minus')
|
||||||
// {{/}}
|
|
||||||
// if Daily, calendar
|
// if Daily, calendar
|
||||||
fieldset(ng-if='task.type=="daily"', class="option-group")
|
// FIXME display, but disable for challenge tasks
|
||||||
|
fieldset(ng-if='task.type=="daily" && !task.challenge.id', class="option-group")
|
||||||
legend.option-title Repeat
|
legend.option-title Repeat
|
||||||
.task-controls.tile-group.repeat-days
|
.task-controls.tile-group.repeat-days
|
||||||
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
|
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.su}', type='button', data-day='su', ng-click='task.repeat.su = !task.repeat.su') Su
|
button.task-action-btn.tile(ng-class='{active: task.repeat.su}', type='button', data-day='su', ng-click='task.repeat.su = !task.repeat.su') Su
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.m}', type='button', data-day='m', ng-click='task.repeat.m = !task.repeat.m') M
|
button.task-action-btn.tile(ng-class='{active: task.repeat.m}', type='button', data-day='m', ng-click='task.repeat.m = !task.repeat.m') M
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.t}', type='button', data-day='t', ng-click='task.repeat.t = !task.repeat.t') T
|
button.task-action-btn.tile(ng-class='{active: task.repeat.t}', type='button', data-day='t', ng-click='task.repeat.t = !task.repeat.t') T
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.w}', type='button', data-day='w', ng-click='task.repeat.w = !task.repeat.w') W
|
button.task-action-btn.tile(ng-class='{active: task.repeat.w}', type='button', data-day='w', ng-click='task.repeat.w = !task.repeat.w') W
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', data-day='th', ng-click='task.repeat.th = !task.repeat.th') Th
|
button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', data-day='th', ng-click='task.repeat.th = !task.repeat.th') Th
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', data-day='f', ng-click='task.repeat.f = !task.repeat.f') F
|
button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', data-day='f', ng-click='task.repeat.f = !task.repeat.f') F
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', data-day='s', ng-click='task.repeat.s = !task.repeat.s') S
|
button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', data-day='s', ng-click='task.repeat.s = !task.repeat.s') S
|
||||||
|
|
||||||
// if Reward, pricing
|
// if Reward, pricing
|
||||||
fieldset.option-group.option-short(ng-if='task.type=="reward"')
|
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
|
||||||
legend.option-title Price
|
legend.option-title Price
|
||||||
input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value')
|
input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value')
|
||||||
.money.input-suffix
|
.money.input-suffix
|
||||||
span.shop_gold
|
span.shop_gold
|
||||||
|
|
||||||
// if Todos, the due date
|
// if Todos, the due date
|
||||||
fieldset.option-group(ng-if='task.type=="todo"')
|
fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
|
||||||
legend.option-title Due Date
|
legend.option-title Due Date
|
||||||
input.option-content.datepicker(type='text', data-date-format='mm/dd/yyyy', ng-model='task.date')
|
input.option-content.datepicker(type='text', data-date-format='mm/dd/yyyy', ng-model='task.date')
|
||||||
|
|
||||||
fieldset.option-group(ng-if='list.main')
|
fieldset.option-group(ng-if='!task.challenge.id')
|
||||||
legend.option-title Tags
|
legend.option-title Tags
|
||||||
label.checkbox(ng-repeat='tag in user.tags')
|
label.checkbox(ng-repeat='tag in user.tags')
|
||||||
input(type='checkbox', ng-model='task.tags[tag.id]')
|
input(type='checkbox', ng-model='task.tags[tag.id]')
|
||||||
| {{tag.name}}
|
| {{tag.name}}
|
||||||
|
|
||||||
// Advanced Options
|
// Advanced Options
|
||||||
span(ng-if='task.type!="reward"')
|
span(ng-if='task.type!="reward"', ng-if='!task.challenge.id')
|
||||||
p.option-title.mega(ng-click='task._advanced = !task._advanced') Advanced Options
|
p.option-title.mega(ng-click='task._advanced = !task._advanced') Advanced Options
|
||||||
fieldset.option-group.advanced-option(ng-class="{visuallyhidden: !task._advanced}")
|
fieldset.option-group.advanced-option(ng-class="{visuallyhidden: !task._advanced}")
|
||||||
legend.option-title
|
legend.option-title
|
||||||
a.priority-multiplier-help(href='https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17', target='_blank', popover-title='How difficult is this task?', popover-trigger='mouseenter', popover="This multiplies its point value. Use sparingly, rely instead on our organic value-adjustment algorithms. But some tasks are grossly more valuable (Write Thesis vs Floss Teeth). Click for more info.")
|
a.priority-multiplier-help(href='https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17', target='_blank', popover-title='How difficult is this task?', popover-trigger='mouseenter', popover="This multiplies its point value. Use sparingly, rely instead on our organic value-adjustment algorithms. But some tasks are grossly more valuable (Write Thesis vs Floss Teeth). Click for more info.")
|
||||||
i.icon-question-sign
|
i.icon-question-sign
|
||||||
| Difficulty
|
| Difficulty
|
||||||
// {{#if taskInChallenge(task)}}
|
|
||||||
// <button disabled type="button" class="task-action-btn tile active">
|
|
||||||
// {{taskAttrFromChallenge(task,'priority')}}
|
|
||||||
// </button>
|
|
||||||
// {{else}}
|
|
||||||
.task-controls.tile-group.priority-multiplier(data-id='{{task.id}}')
|
.task-controls.tile-group.priority-multiplier(data-id='{{task.id}}')
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!" || !task.priority}', ng-click='task.priority="!"') Easy
|
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!" || !task.priority}', ng-click='task.priority="!"') Easy
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!"}', ng-click='task.priority="!!"') Medium
|
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!"}', ng-click='task.priority="!!"') Medium
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!!"}', ng-click='task.priority="!!!"') Hard
|
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!!"}', ng-click='task.priority="!!!"') Hard
|
||||||
// {{/}}
|
span(ng-if='task.type=="daily" && !task.challenge.id')
|
||||||
span(ng-if='task.type=="daily"')
|
|
||||||
legend.option-title Restore Streak
|
legend.option-title Restore Streak
|
||||||
input.option-content(type='number', ng-model='task.streak')
|
input.option-content(type='number', ng-model='task.streak')
|
||||||
button.task-action-btn.tile.spacious(type='submit') Save & Close
|
button.task-action-btn.tile.spacious(type='submit') Save & Close
|
||||||
|
|
||||||
div(class='{{task.id}}-chart', ng-show='charts[task.id]')
|
div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]')
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
div(ng-if='authenticated() && !user.purchased.ads')
|
|
||||||
span.pull-right(ng-if='list.type!="reward"')
|
|
||||||
a(ng-click='modals.buyGems=true', tooltip='Remove Ads')
|
|
||||||
i.icon-remove
|
|
||||||
|
|
||||||
div(ng-if='list.type=="habit"', habitrpg-adsense)
|
|
||||||
script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js')
|
|
||||||
// Habit3
|
|
||||||
ins.adsbygoogle(style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576')
|
|
||||||
script.
|
|
||||||
(adsbygoogle = window.adsbygoogle || []).push({});
|
|
||||||
|
|
||||||
div(ng-if='list.type=="daily"', habitrpg-adsense)
|
|
||||||
script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js')
|
|
||||||
// Habit3
|
|
||||||
ins.adsbygoogle(style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576')
|
|
||||||
script.
|
|
||||||
(adsbygoogle = window.adsbygoogle || []).push({});
|
|
||||||
|
|
||||||
div(ng-if='list.type=="todo"', habitrpg-adsense)
|
|
||||||
script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js')
|
|
||||||
// Habit3
|
|
||||||
ins.adsbygoogle(style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576')
|
|
||||||
script.
|
|
||||||
(adsbygoogle = window.adsbygoogle || []).push({});
|
|
||||||
Reference in New Issue
Block a user