Merge branch 'develop' of github.com:lefnire/habitrpg into develop

This commit is contained in:
Michał Barciś 2013-06-05 23:38:35 +02:00
commit 0b9a6bfaeb
26 changed files with 1304 additions and 464 deletions

View file

@ -0,0 +1,48 @@
/**
* In adding the Guilds feature (which supports the Challenges feature), we are consolidating parties and guilds
* into one collection: groups, with group.type either 'party' or 'guild'. We are also creating the 'habitrpg' guild,
* which everyone is auto-subscribed to, and moving tavern chat into that guild
*
* mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20130518_setup_groups.js
*/
/**
* TODO
* 1) rename collection parties => groups
* 2) add group.type = 'party' for each current group
* 3) create habitrpg group, .type='guild'
* 4) move tavern.chat.chat into habitrpg guild
* 5) subscribe everyone to habitrpg (be sure to set that for default user too!)
*/
db.parties.renameCollection('groups',true);
//db.parties.dropCollection(); // doesn't seem to do this step during rename...
//db.parties.ensureIndex( { 'members': 1, 'background': 1} );
db.groups.update({}, {$set:{type:'party'}}, {multi:true});
//migrate invitation mechanisms
db.users.update(
{},
{
$remove:{party:1},
$set:{invitations:{party:null,guilds:[]}}
},
{multi:1}
);
tavern = db.tavern.findOne();
db.tavern.drop();
//TODO make as a callback of previous, or make sure group.type is still 'guild' for habitrpg in the end
db.groups.insert({
_id: "habitrpg",
leader: '9',
type: 'guild',
name: "HabitRPG",
chat: tavern.messages,
info: {
blurb: '',
websites: []
}
});

View file

@ -0,0 +1,25 @@
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130602_survey_rewards.js
var members = []
members = _.uniq(members);
var query = {
_id: {$exists:1},
$or:[
{_id: {$in: members}},
//{'profile.name': {$in: members}},
{'auth.facebook.name': {$in: members}},
{'auth.local.username': {$in: members}},
{'auth.local.email': {$in: members}}
]
};
print(db.users.count(query));
db.users.update(query,
{
$set: { 'achievements.helpedHabit': true },
$inc: { balance: 2.5 }
},
{multi:true}
)

View file

@ -1,10 +1,10 @@
import csv
data = csv.reader(open('/home/slappybag/backrs/800dollar.csv', 'rb'), delimiter=",", quotechar='|')
column = []
with open(r"/home/slappybag/Documents/SurveyScrape.csv") as f:
reader = csv.reader(f, delimiter=',', quotechar='"')
column = []
for row in reader:
if row:
column.append(row[4])
for row in data:
column.append(row[9])
print "one:"
print column

83
src/app/challenges.coffee Normal file
View file

@ -0,0 +1,83 @@
_ = require 'lodash'
helpers = require 'habitrpg-shared/script/helpers'
module.exports.app = (appExports, model) ->
browser = require './browser'
user = model.at '_user'
$('#profile-challenges-tab-link').on 'show', (e) ->
_.each model.get('groups'), (g) ->
_.each g.challenges, (chal) ->
_.each ['habit','daily','todo'], (type) ->
_.each chal["#{type}s"], (task) ->
_.each chal.users, (member) ->
if (history = member?["#{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)
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
appExports.challengeUnsubscribe = (e) ->
chal = e.get()
i = user.get('challenges')?.indexOf chal.id
user.remove("challenges.#{i}") if i? and i != -1
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
_.each chal["#{type}s"], (task) ->
model.remove "_#{type}List", _.findIndex(model.get("_#{type}List",{id:task.id}))
model.del "_user.tasks.#{task.id}"
true

185
src/app/groups.coffee Normal file
View file

@ -0,0 +1,185 @@
_ = require('lodash')
helpers = require('habitrpg-shared/script/helpers')
module.exports.app = (appExports, model, app) ->
browser = require './browser'
_currentTime = model.at '_currentTime'
_currentTime.setNull +new Date
# Every 60 seconds, reset the current time so that the chat can update relative times
setInterval (->_currentTime.set +new Date), 60000
user = model.at('_user')
appExports.groupCreate = (e,el) ->
type = $(el).attr('data-type')
newGroup =
name: model.get("_new.group.name")
description: model.get("_new.group.description")
leader: user.get('id')
members: [user.get('id')]
type: type
# parties - free
if type is 'party'
return model.add 'groups', newGroup, ->location.reload()
# guilds - 4G
unless user.get('balance') >= 1
return $('#more-gems-modal').modal 'show'
if confirm "Create Guild for 4 Gems?"
newGroup.privacy = (model.get("_new.group.privacy") || 'public') if type is 'guild'
newGroup.balance = 1 # they spent $ to open the guild, it goes into their guild bank
model.add 'groups', newGroup, ->
user.incr 'balance', -1, ->location.reload()
appExports.toggleGroupEdit = (e, el) ->
path = "_editing.groups.#{$(el).attr('data-gid')}"
model.set path, !model.get(path)
appExports.toggleLeaderMessageEdit = (e, el) ->
path = "_editing.leaderMessage.#{$(el).attr('data-gid')}"
model.set path, !model.get(path)
appExports.groupAddWebsite = (e, el) ->
test = e.get()
e.at().unshift 'websites', model.get('_newGroupWebsite')
model.del '_newGroupWebsite'
appExports.groupInvite = (e,el) ->
uid = model.get('_groupInvitee').replace(/[\s"]/g, '')
model.set '_groupInvitee', ''
return if _.isEmpty(uid)
model.query('users').publicInfo([uid]).fetch (err, profiles) ->
throw err if err
profile = profiles.at(0).get()
return model.set("_groupError", "User with id #{uid} not found.") unless profile
model.query('groups').withMember(uid).fetch (err, g) ->
throw err if err
group = e.get(); groups = g.get()
{type, name} = group; gid = group.id
groupError = (msg) -> model.set("_groupError", msg)
invite = ->
$.bootstrapGrowl "Invitation Sent."
switch type
when 'guild' then model.push "users.#{uid}.invitations.guilds", {id:gid, name}, ->location.reload()
when 'party' then model.set "users.#{uid}.invitations.party", {id:gid, name}, ->location.reload()
switch type
when 'guild'
if profile.invitations?.guilds and _.find(profile.invitations.guilds, {id:gid})
return groupError("User already invited to that group")
else if uid in group.members
return groupError("User already in that group")
else invite()
when 'party'
if profile.invitations?.party
return groupError("User already pending invitation.")
else if _.find(groups, {type:'party'})
return groupError("User already in a party.")
else invite()
joinGroup = (gid) ->
model.push("groups.#{gid}.members", user.get('id'), ->location.reload())
appExports.joinGroup = (e, el) -> joinGroup e.get('id')
appExports.acceptInvitation = (e,el) ->
gid = e.get('id')
if $(el).attr('data-type') is 'party'
user.set 'invitations.party', null, ->joinGroup(gid)
else
e.at().remove ->joinGroup(gid)
appExports.rejectInvitation = (e, el) ->
clear = -> browser.resetDom(model)
if e.at().path().indexOf('party') != -1
model.del e.at().path(), clear
else e.at().remove clear
appExports.groupLeave = (e,el) ->
if confirm("Leave this group, are you sure?") is true
uid = user.get('id')
group = model.at "groups.#{$(el).attr('data-id')}"
index = group.get('members').indexOf(uid)
if index != -1
group.remove 'members', index, 1, ->
updated = group.get()
# last member out, delete the party
if _.isEmpty(updated.members) and (updated.type is 'party')
group.del ->location.reload()
# assign new leader, so the party is editable #TODO allow old leader to assign new leader, this is just random
else if (updated.leader is uid)
group.set "leader", updated.members[0], ->location.reload()
else location.reload()
###
Chat Functionality
###
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
model.on 'unshift', '_habitrpg.chat', -> $('.chat-message').tooltip()
appExports.sendChat = (e,el) ->
text = model.get '_chatMessage'
# Check for non-whitespace characters
return unless /\S/.test text
group = e.at()
# get rid of duplicate member ids - this is a weird place to put it, but works for now
members = group.get('members'); uniqMembers = _.uniq(members)
group.set('members', uniqMembers) if !_.isEqual(uniqMembers, members)
chat = group.at('chat')
model.set('_chatMessage', '')
message =
id: model.id()
uuid: user.get('id')
contributor: user.get('backer.contributor')
npc: user.get('backer.npc')
text: text
user: helpers.username(model.get('_user.auth'), model.get('_user.profile.name'))
timestamp: +new Date
# FIXME - sometimes racer will send many duplicates via chat.unshift. I think because it can't make connection, keeps
# trying, but all attempts go through. Unfortunately we can't do chat.set without potentially clobbering other chatters,
# and we can't make chat an object without using refLists. hack solution for now is to unshift, and if there are dupes
# after we set to unique
chat.unshift message, ->
messages = chat.get() || []
count = messages.length
messages =_.uniq messages, true, ((m) -> m?.id) # get rid of dupes
#There were a bunch of duplicates, let's clean it up
if messages.length != count
messages.splice(200)
chat.set messages
else
chat.remove(200)
type = $(el).attr('data-type')
model.set '_user.party.lastMessageSeen', chat.get()[0].id if group.get('type') is 'party'
appExports.chatKeyup = (e, el, next) ->
return next() unless e.keyCode is 13
appExports.sendChat(e, el)
appExports.deleteChatMessage = (e) ->
if confirm("Delete chat message?") is true
e.at().remove() #requires the {#with}
app.on 'render', (ctx) ->
$('#party-tab-link').on 'shown', (e) ->
messages = model.get('_party.chat')
return false unless messages?.length > 0
model.set '_user.party.lastMessageSeen', messages[0].id
appExports.gotoPartyChat = ->
model.set '_gamePane', true, ->
$('#party-tab-link').tab('show')
appExports.assignGroupLeader = (e, el) ->
newLeader = model.get('_new.groupLeader')
if newLeader and (confirm("Assign new leader, you sure?") is true)
e.at().set('leader', newLeader, ->browser.resetDom(model)) if newLeader

View file

@ -30,12 +30,18 @@ algos = require 'habitrpg-shared/script/algos'
setupSubscriptions = (page, model, params, next, cb) ->
uuid = model.get('_userId') or model.session.userId # see http://goo.gl/TPYIt
selfQ = model.query('users').withId(uuid) #keep this for later
partyQ = model.query('parties').withMember(uuid)
partyQ.fetch (err, party) ->
# Note: due to https://github.com/codeparty/racer/issues/57, this has to come at the very beginning. The more limited
# the returned fields in motifs, the sooner they must come in fetch / subscribes.
publicGroupsQuery = model.query('groups').publicGroups()
myGroupsQuery = model.query('groups').withMember(uuid)
model.fetch publicGroupsQuery, myGroupsQuery, (err, publicGroups, groups) ->
return next(err) if err
finished = (descriptors, paths) ->
# Add public "Tavern" guild in
descriptors.unshift('groups.habitrpg'); paths.unshift('_habitRPG')
# Subscribe to each descriptor
model.subscribe.apply model, descriptors.concat ->
[err, refs] = [arguments[0], arguments]
return next(err) if err
@ -45,20 +51,40 @@ setupSubscriptions = (page, model, params, next, cb) ->
return page.redirect('/logout') #delete model.session.userId
return cb()
# (1) Solo player
return finished([selfQ, 'tavern'], ['_user', '_tavern']) unless party.get()
# Get public groups first, order most-to-least # subscribers
model.set '_publicGroups', _.sortBy(publicGroups.get(), (g) -> -_.size(g.members))
groupsObj = groups.get()
# (1) Solo player
return finished([selfQ], ['_user']) if _.isEmpty(groupsObj)
## (2) Party or Guild has members, fetch those users too
# Subscribe to the groups themselves. We separate them by _party, _guilds, and _habitRPG (the "global" guild).
groupsInfo = _.reduce groupsObj, ((m,g)->
if g.type is 'guild' then m.guildIds.push(g.id) else m.partyId = g.id
m.members = m.members.concat(g.members)
m
), {guildIds:[], partyId:null, members:[]}
# Fetch, not subscribe. There's nothing dynamic we need from members, just the the Group (below) which includes chat, challenges, etc
model.query('users').publicInfo(groupsInfo.members).fetch (err, members) ->
return next(err) if err
# we need _members as an object in the view, so we can iterate over _party.members as :id, and access _members[:id] for the info
mObj = members.get()
model.set "_members", _.object(_.pluck(mObj,'id'), mObj)
model.set "_membersArray", mObj
## (2) Party has members, subscribe to those users too
if m = party.get('members')
# Fetch instead of subscribe. There's nothing dynamic we need from members just yet, they'll update _party instead.
# This may change in the future.
model.query('users').party(m).fetch (err, members) ->
return next(err) if err
model.ref '_partyMembers', members
return finished([partyQ, selfQ, 'tavern'], ['_party', '_user', '_tavern'])
else
# Note - selfQ *must* come after membersQ in subscribe, otherwise _user will only get the fields restricted by party-members in store.coffee. Strang bug, but easy to get around
return finished([partyQ, selfQ, 'tavern'], ['_party', '_user', '_tavern'])
descriptors = [selfQ]; paths = ['_user']
if groupsInfo.partyId
descriptors.unshift model.query('groups').withIds(groupsInfo.partyId)
paths.unshift '_party'
unless _.isEmpty(groupsInfo.guildIds)
descriptors.unshift model.query('groups').withIds(groupsInfo.guildIds)
paths.unshift '_guilds'
finished descriptors, paths
# ========== ROUTES ==========
@ -78,15 +104,13 @@ get '/', (page, model, params, next) ->
# ========== CONTROLLER FUNCTIONS ==========
ready (model) ->
exports.removeAt = (e) -> e.at().remove() # used for things like remove website, chat, etc
user = model.at('_user')
misc.fixCorruptUser(model) # https://github.com/lefnire/habitrpg/issues/634
browser = require './browser'
require('./tasks').app(exports, model)
require('./items').app(exports, model)
require('./party').app(exports, model, app)
require('./groups').app(exports, model, app)
require('./profile').app(exports, model)
require('./pets').app(exports, model)
require('../server/private').app(exports, model)
@ -94,6 +118,14 @@ ready (model) ->
browser.app(exports, model, app)
require('./unlock').app(exports, model)
require('./filters').app(exports, model)
require('./challenges').app(exports, model)
# used for things like remove website, chat, etc
exports.removeAt = (e, el) ->
if (confirmMessage = $(el).attr 'data-confirm')?
return unless confirm(confirmMessage) is true
e.at().remove()
browser.resetDom(model) if $(el).attr('data-refresh')
###
Cron
@ -102,7 +134,10 @@ ready (model) ->
# habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks
_.each ['habit','daily','todo','reward'], (type) -> uObj["#{type}s"] = _.where(uObj.tasks, {type}); true
algos.cron uObj, {paths}
# for new user, just set lastCron - no need to reset dom.
# remember that the properties are set from uObj & paths AFTER the return of this callback
return if _.isEmpty(paths) or (paths['lastCron'] and _.size(paths) is 1)
# for everyone else, we need to reset dom - too many changes have been made and won't it breaks dom listeners.
if lostHp = delete paths['stats.hp'] # we'll set this manually so we can get a cool animation
setTimeout ->
browser.resetDom(model)

View file

@ -18,9 +18,24 @@ module.exports.batchTxn = batchTxn = (model, cb, options) ->
# pass true if we have levelled to supress xp notification
unless _.isEmpty paths
setOps = _.reduce paths, ((m,v,k)-> m[k] = helpers.dotGet(k,uObj);m), {}
user.set "update__", setOps
user.set "update__", setOps, options?.done
ret
#TODO put this in habitrpg-shared
###
We can't always use refLists, but we often still need to get a positional path by id: eg, users.1234.tasks.5678.value
For arrays (which use indexes, not id-paths), here's a helper function so we can run indexedPath('users',:user.id,'tasks',:task.id,'value)
###
indexedPath = ->
_.reduce arguments, (m,v) =>
return v if !m #first iteration
return "#{m}.#{v}" if _.isString v #string paths
return "#{m}." + _.findIndex(@model.get(m),v)
, ''
taskInChallenge = (task) ->
return undefined unless task?.challenge
@model.at indexedPath.call(@, "groups.#{task.group.id}.challenges", {id:task.challenge}, "#{task.type}s", {id:task.id})
###
algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the
@ -29,8 +44,8 @@ module.exports.batchTxn = batchTxn = (model, cb, options) ->
perform the updates while tracking paths, then all the values at those paths
###
module.exports.score = (model, taskId, direction, allowUndo=false) ->
#return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0
batchTxn model, (uObj, paths) ->
drop = undefined
delta = batchTxn model, (uObj, paths) ->
tObj = uObj.tasks[taskId]
# Stuff for undo
@ -44,10 +59,32 @@ module.exports.score = (model, taskId, direction, allowUndo=false) ->
delta = algos.score(uObj, tObj, direction, {paths})
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
if uObj._tmp?.drop and $?
model.set '_drop', uObj._tmp.drop
drop = uObj._tmp?.drop
# Update challenge statistics
# FIXME put this in it's own batchTxn, make batchTxn model.at() ref aware (not just _user)
# FIXME use reflists for users & challenges
if (chalTask = taskInChallenge.call({model}, tObj)) and chalTask?.get()
model._dontPersist = false
chalTask.incr "value", delta
chal = model.at indexedPath.call({model}, "groups.#{tObj.group.id}.challenges", {id:tObj.challenge})
chalUser = -> indexedPath.call({model}, chal.path(), 'users', {id:uObj.id})
cu = model.at chalUser()
unless cu?.get()
chal.push "users", {id: uObj.id, name: helpers.username(uObj.auth, uObj.profile?.name)}
cu = model.at chalUser()
else
cu.set 'name', helpers.username(uObj.auth, uObj.profile?.name) # update their name incase it changed
cu.set "#{tObj.type}s.#{tObj.id}",
value: tObj.value
history: tObj.history
model._dontPersist = true
, done:->
if drop and $?
model.set '_drop', drop
$('#item-dropped-modal').modal 'show'
delta
delta
###
Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116
@ -76,14 +113,15 @@ module.exports.fixCorruptUser = (model) ->
user.del("tasks.#{key}")
delete tasks[key]
true
resetDom = false
batchTxn model, (uObj, paths, batch) ->
## fix https://github.com/lefnire/habitrpg/issues/1086
uniqPets = _.uniq(uObj.items.pets)
batch.set('items.pets', uniqPets) if !_.isEqual(uniqPets, uObj.items.pets)
console.log {uniqPets, count:_.size(uniqPets)}
if uObj.invitations?.guilds
uniqInvites = _.uniq(uObj.invitations.guilds)
batch.set('invitations.guilds', uniqInvites) if !_.isEqual(uniqInvites, uObj.invitations.guilds)
## Task List Cleanup
['habit','daily','todo','reward'].forEach (type) ->
@ -91,7 +129,7 @@ module.exports.fixCorruptUser = (model) ->
# 1. remove duplicates
# 2. restore missing zombie tasks back into list
idList = uObj["#{type}Ids"]
taskIds = _.pluck( _.where(tasks, {type:type}), 'id')
taskIds = _.pluck( _.where(tasks, {type}), 'id')
union = _.union idList, taskIds
# 2. remove empty (grey) tasks
@ -128,12 +166,14 @@ module.exports.viewHelpers = (view) ->
view.fn 'int',
get: (num) -> num
set: (num) -> [parseInt(num)]
view.fn 'indexedPath', indexedPath
#iCal
view.fn "encodeiCalLink", helpers.encodeiCalLink
#User
view.fn "gems", (balance) -> return balance/0.25
view.fn "gems", (balance) -> balance * 4
view.fn "username", helpers.username
view.fn "tnl", algos.tnl
view.fn 'equipped', helpers.equipped
@ -162,3 +202,15 @@ module.exports.viewHelpers = (view) ->
#Tags
view.fn 'noTags', helpers.noTags
view.fn 'appliedTags', helpers.appliedTags
#Challenges
view.fn 'taskInChallenge', (task) ->
taskInChallenge.call(@,task)?.get()
view.fn 'taskAttrFromChallenge', (task, attr) ->
taskInChallenge.call(@,task)?.get(attr)
view.fn 'brokenChallengeLink', (task) ->
task?.challenge and !(taskInChallenge.call(@,task)?.get())
view.fn 'challengeMemberScore', (member, tType, tid) ->
Math.round(member["#{tType}s"]?[tid]?.value)

View file

@ -1,147 +0,0 @@
_ = require('lodash')
helpers = require('habitrpg-shared/script/helpers')
module.exports.app = (appExports, model, app) ->
browser = require './browser'
_currentTime = model.at '_currentTime'
_currentTime.setNull +new Date()
# Every 60 seconds, reset the current time so that the chat
# can update relative times
setInterval ->
_currentTime.set +new Date()
, 60000
user = model.at('_user')
model.on 'set', '_user.party.invitation', (after, before) ->
if !before? and after? # they just got invited
partyQ = model.query('parties').withId(after)
partyQ.fetch (err, party) ->
return next(err) if err
model.ref '_party', party
browser.resetDom(model)
appExports.partyCreate = ->
newParty = model.get("_newParty")
id = model.add 'parties', { name: newParty, leader: user.get('id'), members: [user.get('id')], invites:[] }
user.set 'party', {current: id, invitation: null, leader: true}, ->
window.location.reload true
appExports.partyInvite = ->
id = model.get('_newPartyMember').replace(/[\s"]/g, '')
return if _.isEmpty(id)
model.query('users').party([id]).fetch (err, users) ->
throw err if err
u = users.at(0).get()
if !u?
model.set "_partyError", "User with id #{id} not found."
return
else if u.party.current? or u.party.invitation?
model.set "_partyError", "User already in a party or pending invitation."
return
else
$.bootstrapGrowl "Invitation Sent."
model.set "users.#{id}.party.invitation", model.get('_party.id'), -> window.location.reload()
#model.set '_newPartyMember', ''
#partySubscribe model
appExports.partyAccept = ->
partyId = user.get('party.invitation')
user.set 'party.invitation', null
user.set 'party.current', partyId
model.at("parties.#{partyId}.members").push user.get('id'), -> window.location.reload()
# model.query('parties').withId(partyId).fetch (err, p) ->
# members = p.get('members')
# members.push user.get('id')
# p.set 'members', members, ->
# window.location.reload true
# partySubscribe model, ->
# p = model.at('_party')
# p.push 'members', user.get('id')
appExports.partyReject = ->
user.set 'party.invitation', null
browser.resetDom(model)
appExports.partyLeave = ->
id = user.set 'party.current', null
party = model.at '_party'
members = party.get('members')
index = members.indexOf(user.get('id'))
party.remove 'members', index, 1, ->
if members.length is 1 # # last member out, kill the party
model.del "parties.#{id}", (-> window.location.reload true)
else
window.location.reload true
###
Chat Functionality
###
sendChat = (path, input) ->
chat = model.at path
text = model.get input
# Check for non-whitespace characters
return unless /\S/.test text
model.set(input, '')
message =
id: model.id()
uuid: user.get('id')
contributor: user.get('backer.contributor')
npc: user.get('backer.npc')
text: text
user: helpers.username(model.get('_user.auth'), model.get('_user.profile.name'))
timestamp: +new Date
# FIXME - sometimes racer will send many duplicates via chat.unshift. I think because it can't make connection, keeps
# trying, but all attempts go through. Unfortunately we can't do chat.set without potentially clobbering other chatters,
# and we can't make chat an object without using refLists. hack solution for now is to unshift, and if there are dupes
# after we set to unique
chat.unshift message, ->
messages = chat.get() || []
count = messages.length
messages =_.uniq messages, true, ((m) -> m?.id) # get rid of dupes
#There were a bunch of duplicates, let's clean it up
if messages.length != count
messages.splice(200)
chat.set messages
else
chat.remove(200)
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
model.on 'unshift', '_tavern.chat.messages', -> $('.chat-message').tooltip()
appExports.partySendChat = ->
sendChat('_party.chat', '_chatMessage')
model.set '_user.party.lastMessageSeen', model.get('_party.chat')[0].id
appExports.tavernSendChat = ->
sendChat('_tavern.chat.messages', '_tavernMessage')
appExports.partyMessageKeyup = (e, el, next) ->
return next() unless e.keyCode is 13
appExports.partySendChat()
appExports.tavernMessageKeyup = (e, el, next) ->
return next() unless e.keyCode is 13
appExports.tavernSendChat()
appExports.deleteChatMessage = (e) ->
if confirm("Delete chat message?") is true
e.at().remove() #requires the {#with}
app.on 'render', (ctx) ->
$('#party-tab-link').on 'shown', (e) ->
messages = model.get('_party.chat')
return false unless messages?.length > 0
model.set '_user.party.lastMessageSeen', messages[0].id
appExports.gotoPartyChat = ->
model.set '_gamePane', true, ->
$('#party-tab-link').tab('show')

View file

@ -25,12 +25,11 @@ module.exports.app = (appExports, model) ->
return alert "You don't own that egg yet, complete more tasks!" if eggIdx is -1
return alert "You already have that pet, hatch a different combo." if myPets and myPets.indexOf("#{egg.name}-#{hatchingPotionName}") != -1
user.push 'items.pets', egg.name + '-' + hatchingPotionName
eggs.splice eggIdx, 1
myHatchingPotion.splice hatchingPotionIdx, 1
user.set 'items.eggs', eggs
user.set 'items.hatchingPotions', myHatchingPotion
user.push 'items.pets', egg.name + '-' + hatchingPotionName, ->
eggs.splice eggIdx, 1
myHatchingPotion.splice hatchingPotionIdx, 1
user.set 'items.eggs', eggs
user.set 'items.hatchingPotions', myHatchingPotion
alert 'Your egg hatched! Visit your stable to equip your pet.'

View file

@ -18,8 +18,9 @@ module.exports.app = (appExports, model) ->
# Don't add a blank task; 20/02/13 Added a check for undefined value, more at issue #463 -lancemanfv
return if /^(\s)*$/.test(text) || text == undefined
activeFilters = _.reduce user.get('filters'), ((memo,v,k) -> memo[k]=v if v;memo), {}
newTask = {id: model.id(), type: type, text: text, notes: '', value: 0, tags: activeFilters}
newTask = {id: model.id(), type, text, notes: '', value: 0}
newTask.tags = _.reduce user.get('filters'), ((memo,v,k) -> memo[k]=v if v; memo), {}
switch type
when 'habit'
newTask = _.defaults {up: true, down: true}, newTask
@ -29,7 +30,7 @@ module.exports.app = (appExports, model) ->
newTask = _.defaults {repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}, completed: false }, newTask
when 'todo'
newTask = _.defaults {completed: false }, newTask
model.unshift "_#{type}List", newTask
e.at().unshift newTask # e.at() in this case is the list, which was scoped here using {#with @list}...{/}
newModel.set ''
appExports.del = (e) ->
@ -74,31 +75,36 @@ module.exports.app = (appExports, model) ->
task.set('repeat.' + $(el).attr('data-day'), true)
appExports.toggleTaskEdit = (e, el) ->
hideId = $(el).attr('data-hide-id')
toggleId = $(el).attr('data-toggle-id')
$(document.getElementById(hideId)).addClass('visuallyhidden')
$(document.getElementById(toggleId)).toggleClass('visuallyhidden')
id = e.get('id')
path = "_tasks.editing.#{id}"
model.set path, !model.get(path)
$(".#{id}-chart").hide()
appExports.toggleChart = (e, el) ->
hideSelector = $(el).attr('data-hide-id')
chartSelector = $(el).attr('data-toggle-id')
historyPath = $(el).attr('data-history-path')
$(document.getElementById(hideSelector)).hide()
$(document.getElementById(chartSelector)).toggle()
id = $(el).attr('data-id')
history = []
if id is 'todos'
model.set "_tasks.charts.todos", !model.get("_tasks.charts.todos")
history = model.get("_user.history.todos")
$(".#{id}-chart").toggle()
else
[id, path] = [$(el).attr('data-id'), "_tasks.charts.#{id}"]
model.set path, !model.get(path)
model.set "_tasks.editing.#{id}", false
$(".#{id}-chart").toggle()
history = model.get("_user.tasks.#{id}.history")
matrix = [['Date', 'Score']]
for obj in model.get(historyPath)
for obj in history
date = +new Date(obj.date)
readableDate = moment(date).format('MM/DD')
matrix.push [ readableDate, obj.value ]
data = google.visualization.arrayToDataTable matrix
options = {
options =
title: 'History'
backgroundColor: { fill:'transparent' }
}
chart = new google.visualization.LineChart(document.getElementById( chartSelector ))
chart = new google.visualization.LineChart $(".#{id}-chart")[0]
chart.draw(data, options)
appExports.todosShowRemaining = -> model.set '_showCompleted', false

View file

@ -5,10 +5,15 @@ Setup read / write access
@param store
###
publicAccess = ->
accept = arguments[arguments.length-2]
#return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
return accept(false) if derbyAuth.bustedSession(@)
accept(true)
module.exports.customAccessControl = (store) ->
userAccess(store)
partySystem(store)
tavernSystem(store)
groupSystem(store)
REST(store)
###
@ -43,7 +48,7 @@ userAccess = (store) ->
return accept(false) # we can only manually set this stuff in the database
# public access to users.*.party.invitation (TODO, lock down a bit more)
if attrPath is 'party.invitation'
if attrPath.indexOf('invitations.') is 0
return accept(true)
# Same session (user.id = this.session.userId)
@ -84,63 +89,63 @@ REST = (store) ->
###
Party permissions
Party & Guild Permissions
###
partySystem = (store) ->
store.query.expose "users", "party", (ids) ->
groupSystem = (store) ->
###
Public User Info
###
store.query.expose "users", "publicInfo", (ids) ->
@where("id").within(ids)
.only('stats',
'items',
'party',
'invitations',
'profile',
'achievements',
'backer',
'preferences',
'auth.local.username',
'auth.facebook.displayName')
store.queryAccess "users", "publicInfo", publicAccess
store.queryAccess "users", "party", (ids, accept, err) ->
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
return accept(false) if derbyAuth.bustedSession(@)
accept(true) # no harm in public user stats
###
Read / Write groups, so they can create new groups
###
store.readPathAccess "groups.*", publicAccess
store.writeAccess "*", "groups.*", publicAccess
store.query.expose "parties", "withId", (id) ->
@where("id").equals(id).findOne()
###
Public HabitRPG Guild
###
store.readPathAccess 'groups.habitrpg', publicAccess
store.writeAccess "*", "groups.habitrpg.chat.*", publicAccess
store.writeAccess "*", "groups.habitrpg.challenges.*", publicAccess
store.queryAccess "parties", "withId", (id, accept, err) ->
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
return accept(false) if derbyAuth.bustedSession(@)
accept(true)
###
Find group which has member by id
###
store.query.expose "groups", "withMember", (id, type) ->
q = @where('members').contains([id]).only(['id', 'type', 'name', 'description', 'members' , 'privacy'])
q = q.where('type').equals(type) if type?
store.queryAccess 'groups', 'withMember', publicAccess
store.readPathAccess "parties.*", ->
accept = arguments[arguments.length-2]
accept(true)
store.writeAccess "*", "parties.*", ->
accept = arguments[arguments.length-2]
err = arguments[arguments.length - 1]
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
return accept(false) if derbyAuth.bustedSession(@)
accept(true)
store.query.expose "parties", "withMember", (id) ->
@where('members').contains([id]).findOne()
store.queryAccess 'parties', 'withMember', (id, accept, err) ->
return accept(false) if derbyAuth.bustedSession(@)
accept(true)
###
LFG / tavern system
###
tavernSystem = (store) ->
store.readPathAccess 'tavern', ->
accept = arguments[arguments.length-2]
return accept(false) if derbyAuth.bustedSession(@)
accept(true)
store.writeAccess "*", "tavern.*", ->
accept = arguments[arguments.length-2]
return accept(false) if derbyAuth.bustedSession(@)
accept(true)
###
Public Groups Info
###
store.query.expose "groups", "publicGroups", ->
@where('privacy').equals('public')
.where('type').equals('guild')
.only(['id', 'type', 'name', 'description', 'members' , 'privacy'])
store.queryAccess "groups", "publicGroups", publicAccess
###
Fetch group info (ie, they just got invited)
###
store.query.expose "groups", "withIds", (ids) ->
return unless ids #FIXME this is sometimes null when ids is array (guilds)
if typeof ids is 'string'
@where("id").equals(ids).findOne() # find a single group
else
@where("id").within(ids) # find multiple groups
store.queryAccess "groups", "withIds", publicAccess

View file

@ -0,0 +1,7 @@
ul.challenge-accordion-header-specs
list-style:none
li
background-color: darken($neutral, 10%)
margin: 2px 5px
float:left

View file

@ -36,4 +36,7 @@
height: 40px
.buttonList li
margin: 5px;
margin: 5px;
.option-group .option-time
padding: 0px 5px

View file

@ -26,6 +26,7 @@
@import "./game-pane.styl";
@import "./backer.styl";
@import "./npcs.styl";
@import "./challenges.styl";
// fix exploding to very wide for some reason
.datepicker
@ -165,4 +166,5 @@ hr
background-color #dfe9ea
padding 1px 3px 1px 3px
.nav li > a
cursor: pointer

View file

@ -42,6 +42,14 @@
width:34px
height:34px
.Pet_Currency_Gem, .Pet_Currency_Gem2x, .Pet_Currency_Gem1x
background: url("/img/sprites/Egg_Sprite_Sheet.png") no-repeat
display:block
.Pet_Currency_Gem {background-position: 0px -510px; width: 51px; height: 45px} /* Not an egg or potion so has a different size */
.Pet_Currency_Gem2x {background-position: -55px -513px; width: 34px; height: 30px}
.Pet_Currency_Gem1x {background-position: -63px -542px; width: 19px; height: 17px}
.inventory-list li
clear:both
.pets-menu > div

View file

@ -14,6 +14,11 @@
<hr/>
<p>
<h4>6/03/2013</h4>
<ul>
<li><a target=_blank href="https://trello.com/card/groups-guilds/50e5d3684fe3a7266b0036d6/84">Guilds!</a> You can now belong to multiple groups, not just your party. There are public and private guilds, think "Subreddits" v "multiple friend groups".</li>
</ul>
<h4>5/27/2013</h4>
<ul>
<li>Get the "Helped Habit Grow" badge by <a href="http://community.habitrpg.com/node/290" target="_blank">filling out this survey.</a></li>

View file

@ -1,7 +1,7 @@
<modals:>
{{#each _partyMembers as :profile}}
<app:modals:modal modalId="avatar-modal-{{:profile.id}}">
<app:avatar:profile profile={{:profile}} />
{{#each _membersArray as :member}}
<app:modals:modal modalId="avatar-modal-{{:member.id}}">
<app:avatar:profile profile={{_members[:member.id]}} />
<@footer>
<button data-dismiss="modal" class="btn btn-success">Ok</button>
</@footer>

226
views/app/challenges.html Normal file
View file

@ -0,0 +1,226 @@
<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>

View file

@ -7,9 +7,9 @@
<li>
<a rel=tooltip title='Edit Tags' x-bind="click:toggleEditingTags"><i class='{#if _editingTags}icon-ok{else}icon-pencil{/}'></i></a>
</li>
{#each users[_userId].tags as :tag}
{#each _user.tags as :tag}
<li class="{#if users[_userId].filters[:tag.id]}active{/}" style='position:relative;'>
{#if _editingTags}
{#if and(_editingTags,not(:tag.challenge))}
<div class="input-append option-group tag-editing" >
<input class="input input-small option-content tag-editing-pill" type="text" value='{:tag.name}' />
<span class="add-on tag-editing-pill"><a class='pull-right' x-bind=click:filtersDeleteTag data-index="{$index}"><i class='icon-trash'></i></a></span>

View file

@ -6,22 +6,21 @@
</span>
<ul class="nav nav-tabs game-tabs">
<li class="active"><a data-toggle='tab' data-target="#profileCustomize"><i class='icon-user'></i> Profile</a></li>
<li><a data-toggle='tab' data-target="#profileParty" id='party-tab-link'><i class='icon-heart'></i> Party</a></li>
<li class="active"><a data-toggle='tab' data-target="#profile-customize"><i class='icon-user'></i> Profile</a></li>
<li><a data-toggle='tab' data-target="#profile-groups" id='party-tab-link'><i class='icon-heart'></i> Groups</a></li>
{#if _user.flags.dropsEnabled}
<li><a data-toggle='tab' data-target="#profileInventory"><i class='icon-gift'></i> Inventory</a></li>
<li><a data-toggle='tab' data-target="#profileStable"><i class='icon-leaf'></i> Stable</a></li>
<li><a data-toggle='tab' data-target="#profile-inventory"><i class='icon-gift'></i> Inventory</a></li>
<li><a data-toggle='tab' data-target="#profile-stable"><i class='icon-leaf'></i> Stable</a></li>
{/if}
<li><a data-toggle='tab' data-target="#profileTavern"><i class='icon-eye-close'></i> Tavern</a></li>
<li><a data-toggle='tab' data-target="#profileAchievements"><i class='icon-certificate'></i> Achievements</a></li>
{{#if _loggedIn}}
<li><a data-toggle='tab' data-target="#profileSettings"><i class='icon-wrench'></i> Settings</a></li>
{{/}}
<li><a data-toggle='tab' data-target="#profile-tavern"><i class='icon-eye-close'></i> Tavern</a></li>
<li><a data-toggle='tab' data-target="#profile-achievements"><i class='icon-certificate'></i> Achievements</a></li>
<!--<li><a data-toggle='tab' data-target="#profile-challenges" id='profile-challenges-tab-link' ><i class='icon-bullhorn'></i> Challenges</a></li>-->
<li><a data-toggle='tab' data-target="#profile-settings"><i class='icon-wrench'></i> Settings</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="profileCustomize">
<div class="tab-pane active" id="profile-customize">
<div class='row-fluid'>
<div class='span4 border-right'>
<app:avatar:customize />
@ -35,15 +34,15 @@
</div>
</div>
<div class="tab-pane" id="profileParty">
<app:party:party />
<div class="tab-pane" id="profile-groups">
<app:groups:groups-pane />
</div>
<div class="tab-pane" id="profileAchievements">
<div class="tab-pane" id="profile-achievements">
<app:avatar:achievements profile="{{_user}}" />
</div>
<div class="tab-pane" id="profileInventory">
<div class="tab-pane" id="profile-inventory">
<div class='row-fluid'>
<div class='span6 border-right'>
<h2>Inventory</h2>
@ -56,83 +55,25 @@
</div>
</div>
<div class="tab-pane" id="profileStable">
<div class="tab-pane" id="profile-stable">
<app:pets:stable />
</div>
<div class="tab-pane" id="profileTavern">
<app:game-pane:tavern />
<div class="tab-pane" id="profile-tavern">
<app:groups:group group={_habitRPG} />
</div>
<div class="tab-pane" id="profileSettings">
{{#if _loggedIn}}
<app:settings:settings-pane />
{{/}}
<div class="tab-pane" id="profile-challenges">
<app:challenges:main />
</div>
<div class="tab-pane" id="profile-settings">
<app:settings:settings-pane />
</div>
</div>
</div>
<tavern:>
<div class='row-fluid'>
<div class='span4 border-right'>
<div class='tavern-pane'>
<table><tr>
<td><div class='NPC-Daniel'></div></td>
<td>
<div class="popover static-popover fade right in">
<div class="arrow"></div>
<h3 class="popover-title">Daniel Johansson</h3>
<div class="popover-content">
Welcome to the Tavern! I'm <a target="_blank" href="http://www.kickstarter.com/profile/2014640723">Daniel</a>, the bar keep. If you want to rest a while (going on vacation? sudden illness?), I'll set you up at the inn - dailies won't hurt you while you're resting. Stay a while & meet the locals.
<div><button x-bind="click:toggleResting" class='btn btn-large btn-success {#if _user.flags.rest}active{/}'>{#if _user.flags.rest}Check Out of Inn{else}Rest In The Inn{/}</button></div>
</div>
</div>
</td>
</tr></table>
</div>
<div class='alert alert-info {#unless _user.flags.rest}hidden{/}'>Whilst resting your dailies are saved and aren't effected by day turn-over. Whether you check out tomorrow or in a weeks time you'll continue in the same state as when you checked in.</div>
<div class=well>
<h3>Resources</h3>
<ul class=unstyled>
<li><h4><a target="_blank" href="http://community.habitrpg.com/forums/lfg">LFG Posts</a></h4></li>
<li><h4><a target="_blank" href="http://www.youtube.com/watch?feature=player_embedded&v=cT5ghzZFfao">Tutorial</a></h4></li>
<li><h4><a target="_blank" href="http://community.habitrpg.com/faq-page">FAQ</a></h4></li>
<li><h4><a target="_blank" href="https://github.com/lefnire/habitrpg/issues?state=open">Report a Problem</a></h4></li>
<li><h4><a target="_blank" href="https://trello.com/board/habitrpg/50e5d3684fe3a7266b0036d6">Request a Feature</a></h4></li>
<li><h4><a target="_blank" href="http://community.habitrpg.com/forum">Community Forum</a></h4></li>
</ul>
</div>
</div>
<div class='span8'>
<h3>Tavern Talk & LFG</h3>
<div class='row-fluid'>
<div class='span3'>
<ul class='unstyled buttonList'>
<li><a class='btn btn-info' style='width:100%' target="_blank" href="http://community.habitrpg.com/faq-page">FAQ</a></li>
<li><a class='btn btn-info' style='width:100%' target="_blank" href="https://github.com/lefnire/habitrpg/issues?state=open">Report a Problem</a></li>
<li><a class='btn btn-info' style='width:100%' target="_blank" href="https://trello.com/board/habitrpg/50e5d3684fe3a7266b0036d6">Request a Feature</a></li>
</ul>
</div>
<div class=span9>
<form x-bind='submit:tavernSendChat'>
<textarea class="span6" rows="3" x-bind='keyup:tavernMessageKeyup'>{_tavernMessage}</textarea><br/>
<input class=btn type=submit value="Send Chat" />
</form>
</div>
</div>
<ul class='unstyled tavern-chat'>
{#each _tavern.chat.messages as :message}
<app:party:chat-message message={{:message}} />
{/}
</ul>
</div>
</div>
<market:>
<!-- pets pane -->

363
views/app/groups.html Normal file
View file

@ -0,0 +1,363 @@
<groups-pane:>
<ul class="nav nav-tabs">
<li class="active"><a data-toggle='tab' data-target="#groups-party">Party</a></li>
<li><a data-toggle='tab' data-target="#groups-guilds">Guilds</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="groups-party">
{#if _party.id}
<app:groups:group group={groups[_party.id]} />
{else if _user.invitations.party}
<!-- #with required for the accept/reject buttons -->
{#with _user.invitations.party as :party}
<h2>You're Invited To {:party.name}</h2>
<a class='btn btn-success' data-type='party' x-bind="click:acceptInvitation">Accept</a>
<a class='btn btn-danger' x-bind="click:rejectInvitation">Reject</a>
{/}
{else}
<h2>Create A Party</h2>
<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:</p>
<pre class=prettyprint>{_user.id}</pre>
<app:groups:create-group type='party' />
{/}
</div>
<div class='tab-pane' id="groups-guilds">
<ul class="nav nav-pills">
<li class=active><a data-toggle='tab' data-target="#groups-guilds-public">Public Guilds</a></li>
{{#each _guilds as :guild}}
<li><a data-toggle='tab' data-target="#groups-guild-{{:guild.id}}">{:guild.name}</a></li>
{{/}}
<li><a data-toggle='tab' data-target="#groups-guild-create">Create Guild</a></li>
</ul>
<div class="tab-content">
<div class='tab-pane active' id='groups-guilds-public'>
<!-- strange bug here - derby paths supposed to work like _user?.invitations?.guilds, wtf? -->
{#if and(_user.invitations,_user.invitations.guilds)}
{#each _user.invitations.guilds as :invitation}
<div>
<h3>You're Invited To {:invitation.name}</h3>
<a class='btn btn-success' data-type='guild' x-bind="click:acceptInvitation">Accept</a>
<a class='btn btn-danger' x-bind="click:rejectInvitation">Reject</a>
</div>
{/}
{/}
<app:groups:public-groups />
</div>
{{#each _guilds as :guild}}
<div class="tab-pane" id="groups-guild-{{:guild.id}}" >
<app:groups:group group={:guild} />
</div>
{{/}}
<div class='tab-pane' id='groups-guild-create'>
<app:groups:create-group type='guild' />
</div>
</div>
</div>
</div>
<public-groups:>
<table class="table table-striped">
{#each _publicGroups as :public}
<tr><td>
<ul class="pull-right challenge-accordion-header-specs">
<li>{count(:public.members)} member(s)</li>
<li>
<!-- join / leave -->
{#if indexOf(:public.members,_user.id)}
<a x-bind="click:groupLeave" data-id={{:public.id}} class='btn btn-small btn-danger'><i class='icon-ban-circle'></i> Leave</a>
{else}
<a x-bind="click:joinGroup" class='btn btn-small btn-success'><i class='icon-ok'></i> Join</a>
{/}
</li>
</ul>
<h4>{:public.name}</h4>
<p>{:public.description}</p>
</td></tr>
{/}
</table>
<create-group:>
<form class=form-horizontal x-bind="submit:groupCreate" data-type={{@type}} >
{#if _groupError}
<div class='alert alert-danger'>{_groupError}</div>
{/}
<div class="control-group whatever-options">
<div class=control-group>
<label class="control-label" for="new-group-name">{{#if equal(@type,'party')}}Party{{else}}Guild{{/}} Name</label>
<div class="controls">
<input required id=new-group-name type=text class="input-medium option-content" placeholder="{{#if equal(@type,'party')}}Party{{else}}Guild{{/}} Name" value="{_new.group.name}" />
</div>
</div>
<div class=control-group>
<label class="control-label" for="new-group-description">Description</label>
<div class="controls">
<textarea id=new-group-description cols=3 class='option-content' placeholder='Description'>{_new.group.description}</textarea>
</div>
</div>
{{#if equal(@type,'guild')}}
<div class=control-group>
<div class=controls>
<label class="radio">
<input type='radio' name='new-group-privacy' checked="{equal('public',_new.group.privacy)}" > Public
</label>
<label class="radio">
<input type='radio' name='new-group-privacy' checked="{equal('private',_new.group.privacy)}" > Invite Only
</label>
<input type="submit" class="btn {#unless and(_new.group.privacy,_new.group.name)}disabled{/}" value="Create" /><span class='gem-cost'>4 Gems</span>
<p><small>The Gem cost promotes high quality guilds and is transferred into your guild's bank so you can use as rewards in the upcoming challenges feature!</small></p>
</div>
</div>
{{else}}
<div class=control-group>
<div class=controls>
<input type="submit" class="btn " value="Create" />
</div>
</div>
{{/}}
</div>
</form>
<group:>
{{#if and(equal(@group.type,'guild'),not(equal(@group.id,'habitrpg')))}}
<a class="pull-right gem-wallet" rel="popover" data-trigger="hover" data-title="Guild Bank" data-content="Gems which your Guild leader can use for prizes in the upcoming <a target=_blank href='https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58'>Challenges</a> feature." data-placement="bottom" data-html=true>
<!--<span class="task-action-btn tile flush bright add-gems-btn"></span>-->
<span class="task-action-btn tile flush neutral"><div class="Gems"></div>{{gems(@group.balance)}} Guild Gems</span>
</a>
{{/}}
<div class='row-fluid'>
<div class='span4'>
{{#if equal(@group.id,'habitrpg')}}
<div class='tavern-pane'>
<table><tr>
<td><div class='NPC-Daniel'></div></td>
<td>
<div class="popover static-popover fade right in">
<div class="arrow"></div>
<h3 class="popover-title">Daniel Johansson</h3>
<div class="popover-content">
Welcome to the Tavern! I'm <a target="_blank" href="http://www.kickstarter.com/profile/2014640723">Daniel</a>, the bar keep. If you want to rest a while (going on vacation? sudden illness?), I'll set you up at the inn - dailies won't hurt you while you're resting. Stay a while & meet the locals.
<div><button x-bind="click:toggleResting" class='btn btn-large btn-success {#if _user.flags.rest}active{/}'>{#if _user.flags.rest}Check Out of Inn{else}Rest In The Inn{/}</button></div>
</div>
</div>
</td>
</tr></table>
</div>
<div class='alert alert-info {#unless _user.flags.rest}hidden{/}'>Whilst resting your dailies are saved and aren't affected by day turn-over. Whether you check out tomorrow or in a weeks time you'll continue in the same state as when you checked in.</div>
<div class=well>
<h3>Resources</h3>
<ul class=unstyled>
<li><h4><a target="_blank" href="http://community.habitrpg.com/forums/lfg">LFG Posts</a></h4></li>
<li><h4><a target="_blank" href="http://www.youtube.com/watch?feature=player_embedded&v=cT5ghzZFfao">Tutorial</a></h4></li>
<li><h4><a target="_blank" href="http://community.habitrpg.com/faq-page">FAQ</a></h4></li>
<li><h4><a target="_blank" href="http://community.habitrpg.com/node/280">Report a Problem</a></h4></li>
<li><h4><a target="_blank" href="https://trello.com/board/habitrpg/50e5d3684fe3a7266b0036d6">Request a Feature</a></h4></li>
<li><h4><a target="_blank" href="http://community.habitrpg.com/forum">Community Forum</a></h4></li>
</ul>
</div>
{{else}}
<h3>{@group.name}</h3>
<div class="accordion" id="accordion-{{@group.id}}-parent">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion-{{@group.id}}-parent" href="#accordion-{{@group.id}}-information">Information</a>
</div>
<div id="accordion-{{@group.id}}-information" class="accordion-body collapse in">
<div class="accordion-inner blah-options">
{#if _editing.groups[@group.id]}
<div style="height:10px">
<a class=pull-right x-bind=click:toggleGroupEdit data-gid={{@group.id}} ><i class=icon-ok></i></a>
</div>
<input type=text value={@group.name} class='option-content' placeholder='Group Name' />
<textarea cols=3 placeholder='Description'>{@group.description}</textarea>
<input type=url class=option-content placeholder="Logo Url" value={@group.logo} />
{#with @group}
<form class='form-inline' x-bind="submit:groupAddWebsite" >
<input type=url placeholder='Website' class='option-content' value={_newGroupWebsite} />
<input type=submit value="Add" />
</form>
<h4>Assign Group Leader</h4>
<select id=group-leader-selection>
{#each @group.members as :memberId}
<option selected="{equal(:memberId,_new.groupLeader)}">{{username(_members[:memberId].auth,_members[:memberId].profile.name)}}</option>
{/}
</select>
<button x-bind=click:assignGroupLeader >Assign</button>
{/}
{#if @group.websites}
<h4>Resources</h4>
<ul class=unstyled>
{#each @group.websites as :website}
<li><a x-bind='click:removeAt'><i class='icon-trash'></i></a> <a target="_blank" href="{:website}" >{:website}</a></li>
{/}
</ul>
{/}
{else}
{#if @group.logo}<img class=pull-right style='max-width:150px' src={@group.logo} />{/}
{{#if equal(@group.leader,_user.id)}}
<a class=pull-right x-bind=click:toggleGroupEdit data-gid={{@group.id}} ><i class=icon-pencil></i></a>
{{/}}
<div>{@group.description}</div>
{#if @group.websites}
<h4>Resources</h4>
<ul class=unstyled>
{#each @group.websites as :website}
<li><a target="_blank" href="{:website}">{:website}</a></li>
{/}
</ul>
{/}
{/}
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion-{{@group.id}}-parent" href="#accordion-{{@group.id}}-members">Members</a>
</div>
<div id="accordion-{{@group.id}}-members" class="accordion-body collapse">
<div class="accordion-inner">
<table class="table table-striped">
{#each @group.members as :memberId}
<tr><td>
<!-- allow leaders to ban members -->
{{#if and(equal(@group.leader,_user.id),not(equal(_user.id,:memberId)))}}
{{#with @group.members[$index]}}
<a x-bind=click:removeAt data-refresh=true data-confirm='Boot this member?'>
<i class=icon-ban-circle rel=tooltip title="Boot Member"></i>
</a>
{{/}}
&nbsp;
{{/}}
<a data-toggle='modal' data-target="#avatar-modal-{{:memberId}}">
<span class="{{#if equal(@group.leader,:memberId)}}badge badge-info{{/}}">
{{username(_members[:memberId].auth, _members[:memberId].profile.name)}}
</span>
</a>
</td>
<td>
({{:memberId}})
</td>
</tr>
{/}
</table>
{#with @group as :group}
<form class="form-inline" x-bind="submit:groupInvite" data-type="{@group.type}" >
{#if _groupError}
<div class='alert alert-danger'>{_groupError}</div>
{/}
<div class='control-group'>
<input type="text" class="input-medium" placeholder="User Id" value="{_groupInvitee}">
<input type="submit" class="btn" value="Invite" />
</div>
</form>
{/}
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion-{{@group.id}}-parent" href="#accordion-{{@group.id}}-challenges">Challenges</a>
</div>
<div id="accordion-{{@group.id}}-challenges" class="accordion-body collapse">
<div class="accordion-inner">
<span class=label><i class=icon-bullhorn></i> Challenges</span> coming soon! <a target="_blank" href="https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58">Details</a>
<!--{#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.
{/}-->
</div>
</div>
</div>
</div>
<a class='btn btn-danger' data-id="{{@group.id}}" x-bind="click:groupLeave">Leave</a>
{{/}}
</div>
<div class='span8'>
{{#if equal(@group.id,'habitrpg')}}
<h3>Tavern Talk & LFG</h3>
<div class='row-fluid'>
<div class='span3'>
<ul class='unstyled buttonList'>
<li><a class='btn btn-info' style='width:100%' target="_blank" href="http://community.habitrpg.com/faq-page">FAQ</a></li>
<li><a class='btn btn-info' style='width:100%' target="_blank" href="http://community.habitrpg.com/node/280">Report a Problem</a></li>
<li><a class='btn btn-info' style='width:100%' target="_blank" href="https://trello.com/board/habitrpg/50e5d3684fe3a7266b0036d6">Request a Feature</a></li>
</ul>
</div>
<div class=span9>
<app:groups:chat-box group={@group} />
</div>
</div>
{{else}}
{{#if equal(@group.leader,_user.id)}}
{#if _editing.leaderMessage[@group.id]}
<a x-bind=click:toggleLeaderMessageEdit data-gid={{@group.id}} class=pull-right><i class=icon-ok></i></a>
<textarea cols=3 placeholder='Message from group leader'>{@group.leaderMessage}</textarea>
{else}
<a x-bind=click:toggleLeaderMessageEdit data-gid={{@group.id}} class='btn pull-right'>Edit leader message</a>
{/}
{{/}}
{#if @group.leaderMessage}
<table><tr>
<td><app:avatar:avatar profile="{{_members[@group.leader]}}" /></td>
<td>
<div class="popover static-popover fade right in">
<div class="arrow"></div>
<h3 class="popover-title">{{username(_members[@group.leader].auth,_members[@group.leader].profile.name)}}</h3>
<div class="popover-content">{@group.leaderMessage}</div>
</div>
</td>
</tr></table>
{/}
<h3>Chat</h3>
<app:groups:chat-box group={@group} />
{{/}}
<ul class='unstyled tavern-chat'>
{#each @group.chat as :message}
<app:groups:chat-message message={{:message}} />
{/}
</ul>
</div>
</div>
<chat-box:>
{{#with @group as :group}}
<form x-bind='submit:sendChat'>
<textarea class="span6" rows="3" x-bind='keyup:chatKeyup'>{_chatMessage}</textarea><br/>
<input class=btn type=submit value="Send Chat" />
</form>
{{/}}
<chat-message:>
<li class="{{#if indexOf(:message.text, username(_user.auth, _user.profile.name))}}highlight{{/if}}">
<span
class="label {{#if @message.npc}}label-success{{else if @message.contributor}}label-inverse{{else if equal(@message.uuid,_user.id)}}label-info{{/}} chat-message"
rel='tooltip' title="{{@message.contributor}}{{@message.npc}}">
{{@message.user}}</span> {{@message.text}} - <span class='muted time'>{relativeDate(@message.timestamp, _currentTime)}
{{#if or(_user.backer.admin,equal(@message.uuid,_user.id))}}{{#with @message}}<a x-bind="click:deleteChatMessage"><i rel=tooltip title=Delete class=icon-remove></i></a>{{/}}{{/}}
</span>
</li>

View file

@ -1,5 +1,5 @@
<header:>
<header class="site-header {#if _user.preferences.hideHeader}hidden{/}" role="banner" data-partySize="{#if gt(_partyMembers.length,1)}{truarr(_partyMembers.length)}{else}0{/}">
<header class="site-header {#if _user.preferences.hideHeader}hidden{/}" role="banner" data-partySize="{#if gt(_party.memebers.length,1)}{truarr(_party.members.length)}{else}0{/}">
<!-- avatar -->
<div class="herobox-wrap main-herobox">
<app:avatar:avatar profile={_user} main=true>
@ -27,11 +27,11 @@
<!-- I have an idea to use this loop for the user's herobox/avatar as well
NOTE TO SELF: Ask Tyler if some kind of inter-leaving is possible
where we can put ONE of the results of this loop BEFORE the progress bars, and the rest after-->
{{#each _partyMembers as :member}}
<div class="herobox-wrap" style="{{#if equal(:member.id, _userId)}}display:none;{{/}}">
{{#unless equal(:member.id, _userId)}}
<app:avatar:avatar profile={{:member}} party="true" />
<!-- Would be way cleaner as a Derby template `data-content="<app:party:member-stats profile={{:member}} />"`, but it was just printing HTML as text -->
{{#each _party.members as :memberId}}
<div class="herobox-wrap" style="{{#if equal(:memberId, _userId)}}display:none;{{/}}">
{{#unless equal(:memberId, _userId)}}
<app:avatar:avatar profile={{_members[:memberId]}} party="true" />
<!-- Would be way cleaner as a Derby template `data-content="<app:groups:member-stats profile={{:member}} />"`, but it was just printing HTML as text -->
<!-- I've re-implemented the rollover using the actual `herobox`/avatar template and data-attributes.
This Derby template idea would have been really handy but this is pretty versatile, and keeps it all in the avatar section -->
{{/}}
@ -39,4 +39,4 @@
{{/}}
<app:alerts:hiding-bailey />
</header>
</header>

View file

@ -6,10 +6,11 @@
<import: src="rewards">
<import: src="footer">
<import: src="settings">
<import: src="party">
<import: src="groups">
<import: src="pets">
<import: src="game-pane">
<import: src="filters">
<import: src="challenges">
<Title:>
HabitRPG | Gamify Your Life

View file

@ -1,68 +0,0 @@
<party:>
{#if _partyMembers}
<div class='row-fluid'>
<div class="span6 border-right">
<h3>{{_party.name}}</h3>
<table class="table table-striped">
{{#each _partyMembers as :member}}
<tr><td>{{username(:member.auth, :member.profile.name)}}</td><td>({{:member.id}})</td></tr>
{{/}}
</table>
<form class=form-inline x-bind="submit: partyInvite">
{#if _partyError}
<div class='alert alert-danger'>{_partyError}</div>
{/}
<div class='control-group'>
<input type="text" class="input-medium" placeholder="User Id" value="{_newPartyMember}">
<input type="submit" class="btn" value="Invite" />
</div>
</form>
<a class='btn btn-danger' x-bind="click: partyLeave">Leave</a>
</div>
<div class="span6">
<h3>Chat</h3>
<form x-bind='submit:partySendChat'>
<textarea class="span6" rows="3" x-bind='keyup:partyMessageKeyup'>{_chatMessage}</textarea><br/>
<input class=btn type=submit value=Submit />
</form>
<ul class='party-chat unstyled'>
{#each _party.chat as :message}
<app:party:chat-message message={{:message}} />
{/}
</ul>
</div>
</div>
{else if _user.party.invitation}
<!-- TODO show by whom -->
<h2>You're Invited To {_party.name}</h2>
<a class='btn btn-success' x-bind="click: partyAccept">Accept</a>
<a class='btn btn-danger' x-bind="click: partyReject">Reject</a>
{else}
<h2>Create A Party</h2>
<!-- Not in a party , no invites - create a new one -->
<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:</p>
<pre class=prettyprint>{_user.id}</pre>
<form class=form-inline x-bind="submit: partyCreate">
{#if _partyError}
<div class='alert alert-danger'>{_partyError}</div>
{/}
<div class=control-group>
<input type="text" class="input-medium" placeholder="Party Name" value="{_newParty}" />
<input type="submit" class="btn" value="Create" />
</div>
</form>
{/}
<chat-message:>
<li class="{{#if indexOf(:message.text, username(_user.auth, _user.profile.name))}}highlight{{/if}}">
<span
class="label {{#if @message.npc}}label-success{{else if @message.contributor}}label-inverse{{else if equal(@message.uuid,_user.id)}}label-info{{/}} chat-message"
rel='tooltip' title="{{@message.contributor}}{{@message.npc}}">
{{@message.user}}</span> {{@message.text}} - <span class='muted time'>{relativeDate(@message.timestamp, _currentTime)}
{{#if or(_user.backer.admin,equal(@message.uuid,_user.id))}}{{#with @message}}<a x-bind="click:deleteChatMessage"><i rel=tooltip title=Delete class=icon-remove></i></a>{{/}}{{/}}
</span>
</li>

View file

@ -35,6 +35,11 @@
<h6>API Token</h6>
<pre class=prettyprint>{_user.apiToken}</pre>
<h6>QR Code</h6>
<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"/>
</div>
</div>

View file

@ -21,7 +21,7 @@
placeHolder="New Habit"
list={@habits}
main={{@main}}
editable={{@editable}}
editable={@editable}
>
<@ads><a href="http://www.amazon.com/gp/product/1400069289/ref=as_li_tf_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1400069289&linkCode=as2&tag=ha0d2-20">The Power of Habit: Why We Do What We Do in Life and Business</a><img src="//www.assoc-amazon.com/e/ir?t=ha0d2-20&l=as2&o=1&a=1400069289" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></@ads>
</app:tasks:task-list>
@ -38,7 +38,7 @@
placeHolder="New Daily"
list={@dailys}
main={{@main}}
editable={{@editable}}
editable={@editable}
>
<@ads><a href="http://www.amazon.com/gp/product/0142000280/ref=as_li_tf_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0142000280&linkCode=as2&tag=ha0d2-20">Getting Things Done: The Art of Stress-Free Productivity</a><img src="//www.assoc-amazon.com/e/ir?t=ha0d2-20&l=as2&o=1&a=0142000280" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></@ads>
</app:tasks:task-list>
@ -53,7 +53,7 @@
<!-- todo export/graph options -->
<span class='option-box pull-right'>
{#if _user.history.todos}
<a class="option-action" x-bind=click:toggleChart data-toggle-id="todos-chart" data-history-path="_user.history.todos" rel=tooltip title="Progress"><i class=icon-signal></i></a>
<a class="option-action" x-bind=click:toggleChart data-id="todos" rel=tooltip title="Progress"><i class=icon-signal></i></a>
{/}
<a class="option-action" href="/v1/users/{{_user.id}}/calendar.ics?apiToken={{_user.apiToken}}" rel=tooltip title="iCal"><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> -->
@ -68,7 +68,7 @@
placeHolder="New Todo"
list={@todos}
main={{@main}}
editable={{@editable}}
editable={@editable}
>
<@ads><a href="http://www.amazon.com/gp/product/0312430000/ref=as_li_tf_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0312430000&linkCode=as2&tag=ha0d2-20">The Checklist Manifesto: How to Get Things Right</a><img src="//www.assoc-amazon.com/e/ir?t=ha0d2-20&l=as2&o=1&a=0312430000" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></@ads>
</app:tasks:task-list>
@ -108,7 +108,7 @@
placeHolder="New Reward"
list={@rewards}
main={{@main}}
editable={{@editable}}
editable={@editable}
>
<@extra>
{{#if @main}}
@ -132,17 +132,22 @@
<task-list: nonvoid>
<h2 class="task-column_title">{{t(@header)}}</h2>
{{#if equal(@type,'todo')}}<div id="todos-chart" class="hidden"></div>{{/}}
{{#if equal(@type,'todo')}}<div class="todos-chart" style='display:none;'></div>{{/}}
{{#if @editable}}
<form class="addtask-form form-inline new-task-form {#if and(_showCompleted,equal(@type,'todo'))}hidden{/}" id="new-{{@type}}" data-task-type="{{@type}}" x-bind="submit:addTask">
<span class="addtask-field"><input value="{@inputValue}" type="text" placeholder="{{@placeHolder}}"/></span>
<input class="addtask-btn" type="submit" value="">
</form>
{#if @editable}
<!-- need {#with} so we can reference model.at() in the submit handler -->
<!-- NOTE: static binding {{}} seems to be required, otherwise things get weird - e.at() is _habitList first time, _habitList.0 second time, etc -->
{{#with @list}}
<form class="addtask-form form-inline new-task-form {#if and(_showCompleted,equal(@type,'todo'))}hidden{/}" data-task-type="{{@type}}" x-bind="submit:addTask">
<span class="addtask-field"><input value="{@inputValue}" type="text" placeholder="{{@placeHolder}}"/></span>
<input class="addtask-btn" type="submit" value="">
</form>
{{/}}
<hr>
{{/}}
{/}
<ul class="{{@type}}s {#unless @list}hidden{/}">
{#each @list as :task}<app:tasks:task />{/}
{#each @list as :task}<app:tasks:task main={{@main}} />{/}
</ul>
{{@extra}}
<br/>
@ -158,7 +163,7 @@
<!-- all the parts of a single task -->
<task:>
<li data-id={{:task.id}} class="task {taskClasses(:task, users[_userId].filters, _user.preferences.dayStart, _user.lastCron, _showCompleted)}">
<li data-id={{:task.id}} class="task {taskClasses(:task, users[_userId].filters, _user.preferences.dayStart, _user.lastCron, _showCompleted, @main)}">
<!-- right-hand side control buttons -->
<div class="task-meta-controls">
@ -168,13 +173,21 @@
</span>
<app:filters:applied-filters />
<!-- edit -->
<a x-bind=click:toggleTaskEdit data-hide-id="{{:task.id}}-chart" data-toggle-id="{{:task.id}}-edit" rel=tooltip title="Edit"><i class="icon-pencil"></i></a>
<!-- delete -->
<a x-bind=click:del rel=tooltip title="Delete"><i class="icon-trash"></i></a>
<a x-bind=click:toggleTaskEdit rel=tooltip title="Edit"><i class="icon-pencil"></i></a>
<!-- challenges -->
{{#if :task.challenge}}
{{#if brokenChallengeLink(:task)}}
<i class='icon-bullhorn' style='background-color:red;' x-bind=click:toggleTaskEdit rel=tooltip title="Broken Challenge Link"></i>
{{else}}
<i class='icon-bullhorn' rel=tooltip title="Challenge Task"></i>
{{/}}
{{else}}
<!-- delete -->
<a x-bind=click:del rel=tooltip title="Delete"><i class="icon-trash"></i></a>
{{/}}
<!-- chart -->
<!-- removing for now cuz it's broken -->
{#if :task.history}
<a x-bind=click:toggleChart data-toggle-id="{{:task.id}}-chart" data-hide-id="{{:task.id}}-edit" data-history-path="_user.tasks.{{:task.id}}.history" rel="tooltip" title="Progress"><i class="icon-signal"></i></a>
<a x-bind=click:toggleChart data-id="{{:task.id}}" rel="tooltip" title="Progress"><i class="icon-signal"></i></a>
{/}
<!-- notes -->
{#if :task.notes}
@ -185,47 +198,82 @@
<!-- left-hand side checkbox -->
<div class="task-controls task-primary">
<!-- Habits -->
{#if equal(:task.type, 'habit')}
{#if :task.up}<a class="task-action-btn" data-direction=up x-bind=click:score></a>{/}
{#if :task.down}<a class="task-action-btn" data-direction=down x-bind=click:score></a>{/}
{{#if equal(:task.type,'habit')}}
{{#if @main}} <!-- only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges -->
{#if :task.up}<a class="task-action-btn" data-direction=up x-bind=click:score></a>{/}
{#if :task.down}<a class="task-action-btn" data-direction=down x-bind=click:score></a>{/}
{{else}}
{#if :task.up}<span class="task-action-btn"></span>{/}
{#if :task.down}<span class="task-action-btn"></span>{/}
{{/}}
<!-- Rewards -->
{else if equal(:task.type, 'reward')}
<a class="money btn-buy" x-bind=click:score data-direction=down>
<span class="reward-cost">{:task.value}</span>
<span class='shop_gold'></span>
</a>
{{else if equal(:task.type,'reward')}}
{{#if @main}} <!-- only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges -->
<a class="money btn-buy" x-bind=click:score data-direction=down>
<span class="reward-cost">{:task.value}</span>
<span class='shop_gold'></span>
</a>
{{else}}
<span class="money btn-buy">
<span class="reward-cost">{:task.value}</span>
<span class='shop_gold'></span>
</span>
{{/}}
<!-- Daily & Todos -->
{else}
<span class="task-checker action-yesno">
<input type=checkbox id="box-{{:task.id}}" class="visuallyhidden focusable" checked="{:task.completed}">
<label for="box-{{:task.id}}"></label>
</span>
{/}
{{else}}
<span class="task-checker action-yesno">
{{#if @main}} <!-- only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges -->
<input type=checkbox id="box-{{:task.id}}" class="visuallyhidden focusable" checked="{:task.completed}">
<label for="box-{{:task.id}}"></label>
{{else}}
<input type=checkbox id="box-{{:task.id}}-static" class="visuallyhidden focusable" checked="false">
<label for="box-{{:task.id}}-static"></label>
{{/}}
</span>
{{/}}
</div>
<!-- main content -->
<p class="task-text">
{:task.text}
{{#if taskInChallenge(:task)}}
{{taskAttrFromChallenge(:task,'text')}}
{{else}}
{:task.text}
{{/}}
</p>
<!-- edit/options dialog -->
<app:tasks:taskMeta />
<app:tasks:taskMeta main={{@main}} />
</li>
<!-- task edit/options -->
<taskMeta:>
<div id="{{:task.id}}-edit" class="task-options visuallyhidden">
<form x-bind=submit:toggleTaskEdit data-toggle-id="{{:task.id}}-edit">
<div class="{#unless _tasks.editing[:task.id]}visuallyhidden{/} task-options">
{{#if brokenChallengeLink(:task)}}
<div class='well'>
<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>
<p><a>Keep</a> | <a>Keep all from challenge</a> | <a>Delete</a> | <a>Delete all from challenge</a></p>
</div>
{{/}}
<form x-bind="submit:toggleTaskEdit" >
<!-- text & notes -->
<fieldset class="option-group">
<label class="option-title">Text</label><input class="option-content" type=text value={:task.text}>
<label class="option-title">Extra Notes</label><textarea class="option-content" rows=3>{:task.notes}</textarea>
{{#unless taskInChallenge(:task)}}
<label class="option-title">Text</label><input class="option-content" type=text value={:task.text}>
{{/}}
<label class="option-title">Extra Notes</label>
{{#if taskInChallenge(:task)}}
<textarea class="option-content" rows=3 disabled>{{taskAttrFromChallenge(:task,'notes')}}</textarea>
{{else}}
<textarea class="option-content" rows=3>{:task.notes}</textarea>
{{/}}
</fieldset>
<!-- if Habit, plus/minus command options -->
{#if equal(:task.type, 'habit')}
{#unless taskInChallenge(:task)}
<fieldset class="option-group">
<legend class="option-title">Direction/Actions</legend>
<span class="task-checker action-plusminus select-toggle">
@ -237,6 +285,7 @@
<label for="{{:task.id}}-option-minus"></label>
</span>
</fieldset>
{/}
<!-- if Daily, calendar -->
{else if equal(:task.type, 'daily')}
@ -272,7 +321,7 @@
{/}
<app:filters:filter-fieldgroup />
{{#if @main}}<app:filters:filter-fieldgroup />{{/}}
<!-- Advanced Options -->
@ -280,11 +329,18 @@
<p x-bind="click:tasksToggleAdvanced" class="option-title mega">Advanced Options</p>
<fieldset class="option-group advanced-option visuallyhidden">
<legend class="option-title"><a class='priority-multiplier-help' href="https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17" target="_blank"><i class='icon-question-sign'></i></a> Difficulty</legend>
<div class="task-controls tile-group priority-multiplier" data-id="{{:task.id}}">
<button type="button" class="task-action-btn tile {#if equal(:task.priority,'!')}active{/}{#unless :task.priority}active{/}" data-priority='!' x-bind=click:tasksSetPriority>Easy</button>
<button type="button" class="task-action-btn tile {#if equal(:task.priority,'!!')}active{/}" data-priority='!!' x-bind=click:tasksSetPriority>Medium</button>
<button type="button" class="task-action-btn tile {#if equal(:task.priority,'!!!')}active{/}" data-priority='!!!' x-bind=click:tasksSetPriority>Hard</button>
</div>
{{#if taskInChallenge(:task)}}
<button disabled type="button" class="task-action-btn tile active">
{{taskAttrFromChallenge(:task,'priority')}}
</button>
{{else}}
<div class="task-controls tile-group priority-multiplier" data-id="{{:task.id}}">
<button type="button" class="task-action-btn tile {#if or(equal(:task.priority,'!'),not(:task.priority))}active{/}" data-priority='!' x-bind=click:tasksSetPriority>Easy</button>
<button type="button" class="task-action-btn tile {#if equal(:task.priority,'!!')}active{/}" data-priority='!!' x-bind=click:tasksSetPriority>Medium</button>
<button type="button" class="task-action-btn tile {#if equal(:task.priority,'!!!')}active{/}" data-priority='!!!' x-bind=click:tasksSetPriority>Hard</button>
</div>
{{/}}
{{#if equal(:task.type,'daily')}}
<legend class="option-title">Restore Streak</legend>
@ -298,4 +354,4 @@
</form>
</div>
<div style="display:none;" id={{:task.id}}-chart></div>
<div style="display:none;" class={{:task.id}}-chart></div>