mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-08-03 12:40:00 +00:00
Merge branch 'develop' of github.com:lefnire/habitrpg into develop
This commit is contained in:
commit
0b9a6bfaeb
26 changed files with 1304 additions and 464 deletions
48
migrations/20130518_setup_groups.js
Normal file
48
migrations/20130518_setup_groups.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* In adding the Guilds feature (which supports the Challenges feature), we are consolidating parties and guilds
|
||||||
|
* into one collection: groups, with group.type either 'party' or 'guild'. We are also creating the 'habitrpg' guild,
|
||||||
|
* which everyone is auto-subscribed to, and moving tavern chat into that guild
|
||||||
|
*
|
||||||
|
* mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20130518_setup_groups.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* 1) rename collection parties => groups
|
||||||
|
* 2) add group.type = 'party' for each current group
|
||||||
|
* 3) create habitrpg group, .type='guild'
|
||||||
|
* 4) move tavern.chat.chat into habitrpg guild
|
||||||
|
* 5) subscribe everyone to habitrpg (be sure to set that for default user too!)
|
||||||
|
*/
|
||||||
|
|
||||||
|
db.parties.renameCollection('groups',true);
|
||||||
|
//db.parties.dropCollection(); // doesn't seem to do this step during rename...
|
||||||
|
//db.parties.ensureIndex( { 'members': 1, 'background': 1} );
|
||||||
|
|
||||||
|
db.groups.update({}, {$set:{type:'party'}}, {multi:true});
|
||||||
|
|
||||||
|
//migrate invitation mechanisms
|
||||||
|
db.users.update(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
$remove:{party:1},
|
||||||
|
$set:{invitations:{party:null,guilds:[]}}
|
||||||
|
},
|
||||||
|
{multi:1}
|
||||||
|
);
|
||||||
|
|
||||||
|
tavern = db.tavern.findOne();
|
||||||
|
db.tavern.drop();
|
||||||
|
|
||||||
|
//TODO make as a callback of previous, or make sure group.type is still 'guild' for habitrpg in the end
|
||||||
|
db.groups.insert({
|
||||||
|
_id: "habitrpg",
|
||||||
|
leader: '9',
|
||||||
|
type: 'guild',
|
||||||
|
name: "HabitRPG",
|
||||||
|
chat: tavern.messages,
|
||||||
|
info: {
|
||||||
|
blurb: '',
|
||||||
|
websites: []
|
||||||
|
}
|
||||||
|
});
|
||||||
25
migrations/20130602_survey_rewards.js
Normal file
25
migrations/20130602_survey_rewards.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130602_survey_rewards.js
|
||||||
|
|
||||||
|
var members = []
|
||||||
|
members = _.uniq(members);
|
||||||
|
|
||||||
|
var query = {
|
||||||
|
_id: {$exists:1},
|
||||||
|
$or:[
|
||||||
|
{_id: {$in: members}},
|
||||||
|
//{'profile.name': {$in: members}},
|
||||||
|
{'auth.facebook.name': {$in: members}},
|
||||||
|
{'auth.local.username': {$in: members}},
|
||||||
|
{'auth.local.email': {$in: members}}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
print(db.users.count(query));
|
||||||
|
|
||||||
|
db.users.update(query,
|
||||||
|
{
|
||||||
|
$set: { 'achievements.helpedHabit': true },
|
||||||
|
$inc: { balance: 2.5 }
|
||||||
|
},
|
||||||
|
{multi:true}
|
||||||
|
)
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
data = csv.reader(open('/home/slappybag/backrs/800dollar.csv', 'rb'), delimiter=",", quotechar='|')
|
with open(r"/home/slappybag/Documents/SurveyScrape.csv") as f:
|
||||||
column = []
|
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
|
print column
|
||||||
83
src/app/challenges.coffee
Normal file
83
src/app/challenges.coffee
Normal 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
185
src/app/groups.coffee
Normal 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
|
||||||
|
|
@ -30,12 +30,18 @@ algos = require 'habitrpg-shared/script/algos'
|
||||||
setupSubscriptions = (page, model, params, next, cb) ->
|
setupSubscriptions = (page, model, params, next, cb) ->
|
||||||
uuid = model.get('_userId') or model.session.userId # see http://goo.gl/TPYIt
|
uuid = model.get('_userId') or model.session.userId # see http://goo.gl/TPYIt
|
||||||
selfQ = model.query('users').withId(uuid) #keep this for later
|
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
|
return next(err) if err
|
||||||
|
|
||||||
finished = (descriptors, paths) ->
|
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 ->
|
model.subscribe.apply model, descriptors.concat ->
|
||||||
[err, refs] = [arguments[0], arguments]
|
[err, refs] = [arguments[0], arguments]
|
||||||
return next(err) if err
|
return next(err) if err
|
||||||
|
|
@ -45,20 +51,40 @@ setupSubscriptions = (page, model, params, next, cb) ->
|
||||||
return page.redirect('/logout') #delete model.session.userId
|
return page.redirect('/logout') #delete model.session.userId
|
||||||
return cb()
|
return cb()
|
||||||
|
|
||||||
# (1) Solo player
|
# Get public groups first, order most-to-least # subscribers
|
||||||
return finished([selfQ, 'tavern'], ['_user', '_tavern']) unless party.get()
|
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
|
# 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 ==========
|
# ========== ROUTES ==========
|
||||||
|
|
||||||
|
|
@ -78,15 +104,13 @@ get '/', (page, model, params, next) ->
|
||||||
# ========== CONTROLLER FUNCTIONS ==========
|
# ========== CONTROLLER FUNCTIONS ==========
|
||||||
|
|
||||||
ready (model) ->
|
ready (model) ->
|
||||||
exports.removeAt = (e) -> e.at().remove() # used for things like remove website, chat, etc
|
|
||||||
|
|
||||||
user = model.at('_user')
|
user = model.at('_user')
|
||||||
misc.fixCorruptUser(model) # https://github.com/lefnire/habitrpg/issues/634
|
misc.fixCorruptUser(model) # https://github.com/lefnire/habitrpg/issues/634
|
||||||
|
|
||||||
browser = require './browser'
|
browser = require './browser'
|
||||||
require('./tasks').app(exports, model)
|
require('./tasks').app(exports, model)
|
||||||
require('./items').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('./profile').app(exports, model)
|
||||||
require('./pets').app(exports, model)
|
require('./pets').app(exports, model)
|
||||||
require('../server/private').app(exports, model)
|
require('../server/private').app(exports, model)
|
||||||
|
|
@ -94,6 +118,14 @@ ready (model) ->
|
||||||
browser.app(exports, model, app)
|
browser.app(exports, model, app)
|
||||||
require('./unlock').app(exports, model)
|
require('./unlock').app(exports, model)
|
||||||
require('./filters').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
|
Cron
|
||||||
|
|
@ -102,7 +134,10 @@ ready (model) ->
|
||||||
# habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks
|
# 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
|
_.each ['habit','daily','todo','reward'], (type) -> uObj["#{type}s"] = _.where(uObj.tasks, {type}); true
|
||||||
algos.cron uObj, {paths}
|
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)
|
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
|
if lostHp = delete paths['stats.hp'] # we'll set this manually so we can get a cool animation
|
||||||
setTimeout ->
|
setTimeout ->
|
||||||
browser.resetDom(model)
|
browser.resetDom(model)
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,24 @@ module.exports.batchTxn = batchTxn = (model, cb, options) ->
|
||||||
# pass true if we have levelled to supress xp notification
|
# pass true if we have levelled to supress xp notification
|
||||||
unless _.isEmpty paths
|
unless _.isEmpty paths
|
||||||
setOps = _.reduce paths, ((m,v,k)-> m[k] = helpers.dotGet(k,uObj);m), {}
|
setOps = _.reduce paths, ((m,v,k)-> m[k] = helpers.dotGet(k,uObj);m), {}
|
||||||
user.set "update__", setOps
|
user.set "update__", setOps, options?.done
|
||||||
ret
|
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
|
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
|
perform the updates while tracking paths, then all the values at those paths
|
||||||
###
|
###
|
||||||
module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
||||||
#return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0
|
drop = undefined
|
||||||
batchTxn model, (uObj, paths) ->
|
delta = batchTxn model, (uObj, paths) ->
|
||||||
tObj = uObj.tasks[taskId]
|
tObj = uObj.tasks[taskId]
|
||||||
|
|
||||||
# Stuff for undo
|
# Stuff for undo
|
||||||
|
|
@ -44,10 +59,32 @@ module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
||||||
|
|
||||||
delta = algos.score(uObj, tObj, direction, {paths})
|
delta = algos.score(uObj, tObj, direction, {paths})
|
||||||
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
|
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
|
||||||
if uObj._tmp?.drop and $?
|
drop = uObj._tmp?.drop
|
||||||
model.set '_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'
|
$('#item-dropped-modal').modal 'show'
|
||||||
delta
|
|
||||||
|
delta
|
||||||
|
|
||||||
###
|
###
|
||||||
Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116
|
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}")
|
user.del("tasks.#{key}")
|
||||||
delete tasks[key]
|
delete tasks[key]
|
||||||
true
|
true
|
||||||
|
|
||||||
resetDom = false
|
resetDom = false
|
||||||
batchTxn model, (uObj, paths, batch) ->
|
batchTxn model, (uObj, paths, batch) ->
|
||||||
|
|
||||||
## fix https://github.com/lefnire/habitrpg/issues/1086
|
## fix https://github.com/lefnire/habitrpg/issues/1086
|
||||||
uniqPets = _.uniq(uObj.items.pets)
|
uniqPets = _.uniq(uObj.items.pets)
|
||||||
batch.set('items.pets', uniqPets) if !_.isEqual(uniqPets, 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
|
## Task List Cleanup
|
||||||
['habit','daily','todo','reward'].forEach (type) ->
|
['habit','daily','todo','reward'].forEach (type) ->
|
||||||
|
|
@ -91,7 +129,7 @@ module.exports.fixCorruptUser = (model) ->
|
||||||
# 1. remove duplicates
|
# 1. remove duplicates
|
||||||
# 2. restore missing zombie tasks back into list
|
# 2. restore missing zombie tasks back into list
|
||||||
idList = uObj["#{type}Ids"]
|
idList = uObj["#{type}Ids"]
|
||||||
taskIds = _.pluck( _.where(tasks, {type:type}), 'id')
|
taskIds = _.pluck( _.where(tasks, {type}), 'id')
|
||||||
union = _.union idList, taskIds
|
union = _.union idList, taskIds
|
||||||
|
|
||||||
# 2. remove empty (grey) tasks
|
# 2. remove empty (grey) tasks
|
||||||
|
|
@ -128,12 +166,14 @@ module.exports.viewHelpers = (view) ->
|
||||||
view.fn 'int',
|
view.fn 'int',
|
||||||
get: (num) -> num
|
get: (num) -> num
|
||||||
set: (num) -> [parseInt(num)]
|
set: (num) -> [parseInt(num)]
|
||||||
|
view.fn 'indexedPath', indexedPath
|
||||||
|
|
||||||
|
|
||||||
#iCal
|
#iCal
|
||||||
view.fn "encodeiCalLink", helpers.encodeiCalLink
|
view.fn "encodeiCalLink", helpers.encodeiCalLink
|
||||||
|
|
||||||
#User
|
#User
|
||||||
view.fn "gems", (balance) -> return balance/0.25
|
view.fn "gems", (balance) -> balance * 4
|
||||||
view.fn "username", helpers.username
|
view.fn "username", helpers.username
|
||||||
view.fn "tnl", algos.tnl
|
view.fn "tnl", algos.tnl
|
||||||
view.fn 'equipped', helpers.equipped
|
view.fn 'equipped', helpers.equipped
|
||||||
|
|
@ -162,3 +202,15 @@ module.exports.viewHelpers = (view) ->
|
||||||
#Tags
|
#Tags
|
||||||
view.fn 'noTags', helpers.noTags
|
view.fn 'noTags', helpers.noTags
|
||||||
view.fn 'appliedTags', helpers.appliedTags
|
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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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')
|
|
||||||
|
|
@ -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 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
|
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
|
user.push 'items.pets', egg.name + '-' + hatchingPotionName, ->
|
||||||
|
eggs.splice eggIdx, 1
|
||||||
eggs.splice eggIdx, 1
|
myHatchingPotion.splice hatchingPotionIdx, 1
|
||||||
myHatchingPotion.splice hatchingPotionIdx, 1
|
user.set 'items.eggs', eggs
|
||||||
user.set 'items.eggs', eggs
|
user.set 'items.hatchingPotions', myHatchingPotion
|
||||||
user.set 'items.hatchingPotions', myHatchingPotion
|
|
||||||
|
|
||||||
alert 'Your egg hatched! Visit your stable to equip your pet.'
|
alert 'Your egg hatched! Visit your stable to equip your pet.'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
# 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
|
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, text, notes: '', value: 0}
|
||||||
newTask = {id: model.id(), type: type, text: text, notes: '', value: 0, tags: activeFilters}
|
newTask.tags = _.reduce user.get('filters'), ((memo,v,k) -> memo[k]=v if v; memo), {}
|
||||||
|
|
||||||
switch type
|
switch type
|
||||||
when 'habit'
|
when 'habit'
|
||||||
newTask = _.defaults {up: true, down: true}, newTask
|
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
|
newTask = _.defaults {repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}, completed: false }, newTask
|
||||||
when 'todo'
|
when 'todo'
|
||||||
newTask = _.defaults {completed: false }, newTask
|
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 ''
|
newModel.set ''
|
||||||
|
|
||||||
appExports.del = (e) ->
|
appExports.del = (e) ->
|
||||||
|
|
@ -74,31 +75,36 @@ module.exports.app = (appExports, model) ->
|
||||||
task.set('repeat.' + $(el).attr('data-day'), true)
|
task.set('repeat.' + $(el).attr('data-day'), true)
|
||||||
|
|
||||||
appExports.toggleTaskEdit = (e, el) ->
|
appExports.toggleTaskEdit = (e, el) ->
|
||||||
hideId = $(el).attr('data-hide-id')
|
id = e.get('id')
|
||||||
toggleId = $(el).attr('data-toggle-id')
|
path = "_tasks.editing.#{id}"
|
||||||
$(document.getElementById(hideId)).addClass('visuallyhidden')
|
model.set path, !model.get(path)
|
||||||
$(document.getElementById(toggleId)).toggleClass('visuallyhidden')
|
$(".#{id}-chart").hide()
|
||||||
|
|
||||||
appExports.toggleChart = (e, el) ->
|
appExports.toggleChart = (e, el) ->
|
||||||
hideSelector = $(el).attr('data-hide-id')
|
id = $(el).attr('data-id')
|
||||||
chartSelector = $(el).attr('data-toggle-id')
|
history = []
|
||||||
historyPath = $(el).attr('data-history-path')
|
|
||||||
$(document.getElementById(hideSelector)).hide()
|
if id is 'todos'
|
||||||
$(document.getElementById(chartSelector)).toggle()
|
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']]
|
matrix = [['Date', 'Score']]
|
||||||
for obj in model.get(historyPath)
|
for obj in history
|
||||||
date = +new Date(obj.date)
|
date = +new Date(obj.date)
|
||||||
readableDate = moment(date).format('MM/DD')
|
readableDate = moment(date).format('MM/DD')
|
||||||
matrix.push [ readableDate, obj.value ]
|
matrix.push [ readableDate, obj.value ]
|
||||||
data = google.visualization.arrayToDataTable matrix
|
data = google.visualization.arrayToDataTable matrix
|
||||||
|
options =
|
||||||
options = {
|
|
||||||
title: 'History'
|
title: 'History'
|
||||||
backgroundColor: { fill:'transparent' }
|
backgroundColor: { fill:'transparent' }
|
||||||
}
|
chart = new google.visualization.LineChart $(".#{id}-chart")[0]
|
||||||
|
|
||||||
chart = new google.visualization.LineChart(document.getElementById( chartSelector ))
|
|
||||||
chart.draw(data, options)
|
chart.draw(data, options)
|
||||||
|
|
||||||
appExports.todosShowRemaining = -> model.set '_showCompleted', false
|
appExports.todosShowRemaining = -> model.set '_showCompleted', false
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,15 @@ Setup read / write access
|
||||||
@param store
|
@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) ->
|
module.exports.customAccessControl = (store) ->
|
||||||
userAccess(store)
|
userAccess(store)
|
||||||
partySystem(store)
|
groupSystem(store)
|
||||||
tavernSystem(store)
|
|
||||||
REST(store)
|
REST(store)
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
@ -43,7 +48,7 @@ userAccess = (store) ->
|
||||||
return accept(false) # we can only manually set this stuff in the database
|
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)
|
# 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)
|
return accept(true)
|
||||||
|
|
||||||
# Same session (user.id = this.session.userId)
|
# Same session (user.id = this.session.userId)
|
||||||
|
|
@ -84,63 +89,63 @@ REST = (store) ->
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
Party permissions
|
Party & Guild Permissions
|
||||||
###
|
###
|
||||||
partySystem = (store) ->
|
groupSystem = (store) ->
|
||||||
store.query.expose "users", "party", (ids) ->
|
|
||||||
|
###
|
||||||
|
Public User Info
|
||||||
|
###
|
||||||
|
store.query.expose "users", "publicInfo", (ids) ->
|
||||||
@where("id").within(ids)
|
@where("id").within(ids)
|
||||||
.only('stats',
|
.only('stats',
|
||||||
'items',
|
'items',
|
||||||
'party',
|
'invitations',
|
||||||
'profile',
|
'profile',
|
||||||
'achievements',
|
'achievements',
|
||||||
'backer',
|
'backer',
|
||||||
'preferences',
|
'preferences',
|
||||||
'auth.local.username',
|
'auth.local.username',
|
||||||
'auth.facebook.displayName')
|
'auth.facebook.displayName')
|
||||||
|
store.queryAccess "users", "publicInfo", publicAccess
|
||||||
|
|
||||||
store.queryAccess "users", "party", (ids, accept, err) ->
|
###
|
||||||
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
|
Read / Write groups, so they can create new groups
|
||||||
return accept(false) if derbyAuth.bustedSession(@)
|
###
|
||||||
accept(true) # no harm in public user stats
|
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(@)
|
Find group which has member by id
|
||||||
return accept(false) if derbyAuth.bustedSession(@)
|
###
|
||||||
accept(true)
|
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]
|
Public Groups Info
|
||||||
accept(true)
|
###
|
||||||
|
store.query.expose "groups", "publicGroups", ->
|
||||||
store.writeAccess "*", "parties.*", ->
|
@where('privacy').equals('public')
|
||||||
accept = arguments[arguments.length-2]
|
.where('type').equals('guild')
|
||||||
err = arguments[arguments.length - 1]
|
.only(['id', 'type', 'name', 'description', 'members' , 'privacy'])
|
||||||
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
|
store.queryAccess "groups", "publicGroups", publicAccess
|
||||||
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)
|
|
||||||
|
|
||||||
|
###
|
||||||
|
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
|
||||||
7
styles/app/challenges.styl
Normal file
7
styles/app/challenges.styl
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
ul.challenge-accordion-header-specs
|
||||||
|
list-style:none
|
||||||
|
|
||||||
|
li
|
||||||
|
background-color: darken($neutral, 10%)
|
||||||
|
margin: 2px 5px
|
||||||
|
float:left
|
||||||
|
|
@ -36,4 +36,7 @@
|
||||||
height: 40px
|
height: 40px
|
||||||
|
|
||||||
.buttonList li
|
.buttonList li
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
|
||||||
|
.option-group .option-time
|
||||||
|
padding: 0px 5px
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
@import "./game-pane.styl";
|
@import "./game-pane.styl";
|
||||||
@import "./backer.styl";
|
@import "./backer.styl";
|
||||||
@import "./npcs.styl";
|
@import "./npcs.styl";
|
||||||
|
@import "./challenges.styl";
|
||||||
|
|
||||||
// fix exploding to very wide for some reason
|
// fix exploding to very wide for some reason
|
||||||
.datepicker
|
.datepicker
|
||||||
|
|
@ -165,4 +166,5 @@ hr
|
||||||
background-color #dfe9ea
|
background-color #dfe9ea
|
||||||
padding 1px 3px 1px 3px
|
padding 1px 3px 1px 3px
|
||||||
|
|
||||||
|
.nav li > a
|
||||||
|
cursor: pointer
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,14 @@
|
||||||
width:34px
|
width:34px
|
||||||
height: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
|
.inventory-list li
|
||||||
clear:both
|
clear:both
|
||||||
.pets-menu > div
|
.pets-menu > div
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<p>
|
<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>
|
<h4>5/27/2013</h4>
|
||||||
<ul>
|
<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>
|
<li>Get the "Helped Habit Grow" badge by <a href="http://community.habitrpg.com/node/290" target="_blank">filling out this survey.</a></li>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<modals:>
|
<modals:>
|
||||||
{{#each _partyMembers as :profile}}
|
{{#each _membersArray as :member}}
|
||||||
<app:modals:modal modalId="avatar-modal-{{:profile.id}}">
|
<app:modals:modal modalId="avatar-modal-{{:member.id}}">
|
||||||
<app:avatar:profile profile={{:profile}} />
|
<app:avatar:profile profile={{_members[:member.id]}} />
|
||||||
<@footer>
|
<@footer>
|
||||||
<button data-dismiss="modal" class="btn btn-success">Ok</button>
|
<button data-dismiss="modal" class="btn btn-success">Ok</button>
|
||||||
</@footer>
|
</@footer>
|
||||||
|
|
|
||||||
226
views/app/challenges.html
Normal file
226
views/app/challenges.html
Normal 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>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a rel=tooltip title='Edit Tags' x-bind="click:toggleEditingTags"><i class='{#if _editingTags}icon-ok{else}icon-pencil{/}'></i></a>
|
<a rel=tooltip title='Edit Tags' x-bind="click:toggleEditingTags"><i class='{#if _editingTags}icon-ok{else}icon-pencil{/}'></i></a>
|
||||||
</li>
|
</li>
|
||||||
{#each users[_userId].tags as :tag}
|
{#each _user.tags as :tag}
|
||||||
<li class="{#if users[_userId].filters[:tag.id]}active{/}" style='position:relative;'>
|
<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" >
|
<div class="input-append option-group tag-editing" >
|
||||||
<input class="input input-small option-content tag-editing-pill" type="text" value='{:tag.name}' />
|
<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>
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,21 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<ul class="nav nav-tabs game-tabs">
|
<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 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="#profileParty" id='party-tab-link'><i class='icon-heart'></i> Party</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}
|
{#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="#profile-inventory"><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-stable"><i class='icon-leaf'></i> Stable</a></li>
|
||||||
{/if}
|
{/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="#profile-tavern"><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>
|
<li><a data-toggle='tab' data-target="#profile-achievements"><i class='icon-certificate'></i> Achievements</a></li>
|
||||||
{{#if _loggedIn}}
|
<!--<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="#profileSettings"><i class='icon-wrench'></i> Settings</a></li>
|
<li><a data-toggle='tab' data-target="#profile-settings"><i class='icon-wrench'></i> Settings</a></li>
|
||||||
{{/}}
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content">
|
<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='row-fluid'>
|
||||||
<div class='span4 border-right'>
|
<div class='span4 border-right'>
|
||||||
<app:avatar:customize />
|
<app:avatar:customize />
|
||||||
|
|
@ -35,15 +34,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="profileParty">
|
<div class="tab-pane" id="profile-groups">
|
||||||
<app:party:party />
|
<app:groups:groups-pane />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="profileAchievements">
|
<div class="tab-pane" id="profile-achievements">
|
||||||
<app:avatar:achievements profile="{{_user}}" />
|
<app:avatar:achievements profile="{{_user}}" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="profileInventory">
|
<div class="tab-pane" id="profile-inventory">
|
||||||
<div class='row-fluid'>
|
<div class='row-fluid'>
|
||||||
<div class='span6 border-right'>
|
<div class='span6 border-right'>
|
||||||
<h2>Inventory</h2>
|
<h2>Inventory</h2>
|
||||||
|
|
@ -56,83 +55,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="profileStable">
|
<div class="tab-pane" id="profile-stable">
|
||||||
<app:pets:stable />
|
<app:pets:stable />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="profileTavern">
|
<div class="tab-pane" id="profile-tavern">
|
||||||
<app:game-pane:tavern />
|
<app:groups:group group={_habitRPG} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="profileSettings">
|
<div class="tab-pane" id="profile-challenges">
|
||||||
{{#if _loggedIn}}
|
<app:challenges:main />
|
||||||
<app:settings:settings-pane />
|
</div>
|
||||||
{{/}}
|
|
||||||
|
<div class="tab-pane" id="profile-settings">
|
||||||
|
<app:settings:settings-pane />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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:>
|
<market:>
|
||||||
|
|
||||||
<!-- pets pane -->
|
<!-- pets pane -->
|
||||||
|
|
|
||||||
363
views/app/groups.html
Normal file
363
views/app/groups.html
Normal 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>
|
||||||
|
{{/}}
|
||||||
|
|
||||||
|
{{/}}
|
||||||
|
<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>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<header:>
|
<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 -->
|
<!-- avatar -->
|
||||||
<div class="herobox-wrap main-herobox">
|
<div class="herobox-wrap main-herobox">
|
||||||
<app:avatar:avatar profile={_user} main=true>
|
<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
|
<!-- 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
|
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-->
|
where we can put ONE of the results of this loop BEFORE the progress bars, and the rest after-->
|
||||||
{{#each _partyMembers as :member}}
|
{{#each _party.members as :memberId}}
|
||||||
<div class="herobox-wrap" style="{{#if equal(:member.id, _userId)}}display:none;{{/}}">
|
<div class="herobox-wrap" style="{{#if equal(:memberId, _userId)}}display:none;{{/}}">
|
||||||
{{#unless equal(:member.id, _userId)}}
|
{{#unless equal(:memberId, _userId)}}
|
||||||
<app:avatar:avatar profile={{:member}} party="true" />
|
<app:avatar:avatar profile={{_members[:memberId]}} 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 -->
|
<!-- 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.
|
<!-- 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 -->
|
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 />
|
<app:alerts:hiding-bailey />
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
<import: src="rewards">
|
<import: src="rewards">
|
||||||
<import: src="footer">
|
<import: src="footer">
|
||||||
<import: src="settings">
|
<import: src="settings">
|
||||||
<import: src="party">
|
<import: src="groups">
|
||||||
<import: src="pets">
|
<import: src="pets">
|
||||||
<import: src="game-pane">
|
<import: src="game-pane">
|
||||||
<import: src="filters">
|
<import: src="filters">
|
||||||
|
<import: src="challenges">
|
||||||
|
|
||||||
<Title:>
|
<Title:>
|
||||||
HabitRPG | Gamify Your Life
|
HabitRPG | Gamify Your Life
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -35,6 +35,11 @@
|
||||||
|
|
||||||
<h6>API Token</h6>
|
<h6>API Token</h6>
|
||||||
<pre class=prettyprint>{_user.apiToken}</pre>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
placeHolder="New Habit"
|
placeHolder="New Habit"
|
||||||
list={@habits}
|
list={@habits}
|
||||||
main={{@main}}
|
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>
|
<@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>
|
</app:tasks:task-list>
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
placeHolder="New Daily"
|
placeHolder="New Daily"
|
||||||
list={@dailys}
|
list={@dailys}
|
||||||
main={{@main}}
|
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>
|
<@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>
|
</app:tasks:task-list>
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
<!-- todo export/graph options -->
|
<!-- todo export/graph options -->
|
||||||
<span class='option-box pull-right'>
|
<span class='option-box pull-right'>
|
||||||
{#if _user.history.todos}
|
{#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 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> -->
|
<!-- <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"
|
placeHolder="New Todo"
|
||||||
list={@todos}
|
list={@todos}
|
||||||
main={{@main}}
|
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>
|
<@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>
|
</app:tasks:task-list>
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
placeHolder="New Reward"
|
placeHolder="New Reward"
|
||||||
list={@rewards}
|
list={@rewards}
|
||||||
main={{@main}}
|
main={{@main}}
|
||||||
editable={{@editable}}
|
editable={@editable}
|
||||||
>
|
>
|
||||||
<@extra>
|
<@extra>
|
||||||
{{#if @main}}
|
{{#if @main}}
|
||||||
|
|
@ -132,17 +132,22 @@
|
||||||
<task-list: nonvoid>
|
<task-list: nonvoid>
|
||||||
<h2 class="task-column_title">{{t(@header)}}</h2>
|
<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}}
|
{#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>
|
<!-- need {#with} so we can reference model.at() in the submit handler -->
|
||||||
<input class="addtask-btn" type="submit" value="+">
|
<!-- NOTE: static binding {{}} seems to be required, otherwise things get weird - e.at() is _habitList first time, _habitList.0 second time, etc -->
|
||||||
</form>
|
{{#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>
|
<hr>
|
||||||
{{/}}
|
{/}
|
||||||
<ul class="{{@type}}s {#unless @list}hidden{/}">
|
<ul class="{{@type}}s {#unless @list}hidden{/}">
|
||||||
{#each @list as :task}<app:tasks:task />{/}
|
{#each @list as :task}<app:tasks:task main={{@main}} />{/}
|
||||||
</ul>
|
</ul>
|
||||||
{{@extra}}
|
{{@extra}}
|
||||||
<br/>
|
<br/>
|
||||||
|
|
@ -158,7 +163,7 @@
|
||||||
|
|
||||||
<!-- all the parts of a single task -->
|
<!-- all the parts of a single task -->
|
||||||
<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 -->
|
<!-- right-hand side control buttons -->
|
||||||
<div class="task-meta-controls">
|
<div class="task-meta-controls">
|
||||||
|
|
@ -168,13 +173,21 @@
|
||||||
</span>
|
</span>
|
||||||
<app:filters:applied-filters />
|
<app:filters:applied-filters />
|
||||||
<!-- edit -->
|
<!-- 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>
|
<a x-bind=click:toggleTaskEdit rel=tooltip title="Edit"><i class="icon-pencil"></i></a>
|
||||||
<!-- delete -->
|
<!-- challenges -->
|
||||||
<a x-bind=click:del rel=tooltip title="Delete"><i class="icon-trash"></i></a>
|
{{#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 -->
|
<!-- chart -->
|
||||||
<!-- removing for now cuz it's broken -->
|
|
||||||
{#if :task.history}
|
{#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 -->
|
<!-- notes -->
|
||||||
{#if :task.notes}
|
{#if :task.notes}
|
||||||
|
|
@ -185,47 +198,82 @@
|
||||||
<!-- left-hand side checkbox -->
|
<!-- left-hand side checkbox -->
|
||||||
<div class="task-controls task-primary">
|
<div class="task-controls task-primary">
|
||||||
<!-- Habits -->
|
<!-- Habits -->
|
||||||
{#if equal(:task.type, 'habit')}
|
{{#if equal(:task.type,'habit')}}
|
||||||
{#if :task.up}<a class="task-action-btn" data-direction=up x-bind=click:score>+</a>{/}
|
{{#if @main}} <!-- only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges -->
|
||||||
{#if :task.down}<a class="task-action-btn" data-direction=down x-bind=click:score>−</a>{/}
|
{#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 -->
|
<!-- Rewards -->
|
||||||
{else if equal(:task.type, 'reward')}
|
{{else if equal(:task.type,'reward')}}
|
||||||
<a class="money btn-buy" x-bind=click:score data-direction=down>
|
{{#if @main}} <!-- only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges -->
|
||||||
<span class="reward-cost">{:task.value}</span>
|
<a class="money btn-buy" x-bind=click:score data-direction=down>
|
||||||
<span class='shop_gold'></span>
|
<span class="reward-cost">{:task.value}</span>
|
||||||
</a>
|
<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 -->
|
<!-- Daily & Todos -->
|
||||||
{else}
|
{{else}}
|
||||||
<span class="task-checker action-yesno">
|
<span class="task-checker action-yesno">
|
||||||
<input type=checkbox id="box-{{:task.id}}" class="visuallyhidden focusable" checked="{:task.completed}">
|
{{#if @main}} <!-- only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges -->
|
||||||
<label for="box-{{:task.id}}"></label>
|
<input type=checkbox id="box-{{:task.id}}" class="visuallyhidden focusable" checked="{:task.completed}">
|
||||||
</span>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- main content -->
|
<!-- main content -->
|
||||||
<p class="task-text">
|
<p class="task-text">
|
||||||
{:task.text}
|
{{#if taskInChallenge(:task)}}
|
||||||
|
{{taskAttrFromChallenge(:task,'text')}}
|
||||||
|
{{else}}
|
||||||
|
{:task.text}
|
||||||
|
{{/}}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- edit/options dialog -->
|
<!-- edit/options dialog -->
|
||||||
<app:tasks:taskMeta />
|
<app:tasks:taskMeta main={{@main}} />
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!-- task edit/options -->
|
<!-- task edit/options -->
|
||||||
<taskMeta:>
|
<taskMeta:>
|
||||||
<div id="{{:task.id}}-edit" class="task-options visuallyhidden">
|
<div class="{#unless _tasks.editing[:task.id]}visuallyhidden{/} task-options">
|
||||||
<form x-bind=submit:toggleTaskEdit data-toggle-id="{{:task.id}}-edit">
|
{{#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 -->
|
<!-- text & notes -->
|
||||||
<fieldset class="option-group">
|
<fieldset class="option-group">
|
||||||
<label class="option-title">Text</label><input class="option-content" type=text value={:task.text}>
|
{{#unless taskInChallenge(:task)}}
|
||||||
<label class="option-title">Extra Notes</label><textarea class="option-content" rows=3>{:task.notes}</textarea>
|
<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>
|
</fieldset>
|
||||||
|
|
||||||
<!-- if Habit, plus/minus command options -->
|
<!-- if Habit, plus/minus command options -->
|
||||||
{#if equal(:task.type, 'habit')}
|
{#if equal(:task.type, 'habit')}
|
||||||
|
{#unless taskInChallenge(:task)}
|
||||||
<fieldset class="option-group">
|
<fieldset class="option-group">
|
||||||
<legend class="option-title">Direction/Actions</legend>
|
<legend class="option-title">Direction/Actions</legend>
|
||||||
<span class="task-checker action-plusminus select-toggle">
|
<span class="task-checker action-plusminus select-toggle">
|
||||||
|
|
@ -237,6 +285,7 @@
|
||||||
<label for="{{:task.id}}-option-minus"></label>
|
<label for="{{:task.id}}-option-minus"></label>
|
||||||
</span>
|
</span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{/}
|
||||||
|
|
||||||
<!-- if Daily, calendar -->
|
<!-- if Daily, calendar -->
|
||||||
{else if equal(:task.type, 'daily')}
|
{else if equal(:task.type, 'daily')}
|
||||||
|
|
@ -272,7 +321,7 @@
|
||||||
{/}
|
{/}
|
||||||
|
|
||||||
|
|
||||||
<app:filters:filter-fieldgroup />
|
{{#if @main}}<app:filters:filter-fieldgroup />{{/}}
|
||||||
|
|
||||||
|
|
||||||
<!-- Advanced Options -->
|
<!-- Advanced Options -->
|
||||||
|
|
@ -280,11 +329,18 @@
|
||||||
<p x-bind="click:tasksToggleAdvanced" class="option-title mega">Advanced Options</p>
|
<p x-bind="click:tasksToggleAdvanced" class="option-title mega">Advanced Options</p>
|
||||||
<fieldset class="option-group advanced-option visuallyhidden">
|
<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>
|
<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>
|
{{#if taskInChallenge(:task)}}
|
||||||
<button type="button" class="task-action-btn tile {#if equal(:task.priority,'!!')}active{/}" data-priority='!!' x-bind=click:tasksSetPriority>Medium</button>
|
<button disabled type="button" class="task-action-btn tile active">
|
||||||
<button type="button" class="task-action-btn tile {#if equal(:task.priority,'!!!')}active{/}" data-priority='!!!' x-bind=click:tasksSetPriority>Hard</button>
|
{{taskAttrFromChallenge(:task,'priority')}}
|
||||||
</div>
|
</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')}}
|
{{#if equal(:task.type,'daily')}}
|
||||||
<legend class="option-title">Restore Streak</legend>
|
<legend class="option-title">Restore Streak</legend>
|
||||||
|
|
@ -298,4 +354,4 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display:none;" id={{:task.id}}-chart></div>
|
<div style="display:none;" class={{:task.id}}-chart></div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue