Merge branch 'develop' into blade-feature/search

This commit is contained in:
Blade Barringer
2015-06-13 00:10:44 -05:00
76 changed files with 1054 additions and 519 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 705 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 693 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -49,7 +49,7 @@
"classBonusText": "Your class (Warrior, if you haven't unlocked or selected another class) uses its own equipment more effectively than gear from other classes. Equipped gear from your current class gets a 50% boost to the attribute bonus it grants.",
"classEquipBonus": "Class Bonus",
"battleGear": "Battle Gear",
"battleGearText": "This is the gear you wear into battle, it affects numbers when interacting with your tasks.",
"battleGearText": "This is the gear you wear into battle; it affects numbers when interacting with your tasks.",
"costume": "Costume",
"costumeText": "If you prefer the look of other gear to what you have equipped, check the \"Use Costume\" box to visually don a costume while wearing your battle gear underneath.",
"useCostume": "Use Costume",

View File

@@ -1,7 +1,7 @@
{
"defaultTaskNotes": " ",
"defaultHabit1Text": "Productive Work (Click the pencil to edit)",
"defaultHabit1Notes": "Sample Good Habits: + Eat a vegetable +15 minutes productive work",
"defaultHabit1Notes": "Sample Good Habits: + Eat a vegetable + 15 minutes productive work",
"defaultHabit2Text": "Eat Junk Food (Click the pencil to edit)",
"defaultHabit2Notes": "Sample Bad Habits: - Smoke - Procrastinate",

View File

@@ -70,7 +70,6 @@
"lastLoggedIn": "- Last logged in",
"notPorted": "This feature is not yet ported from the original site.",
"buyThis": "Buy this <%= text %> with <%= price %> of your <%= gems %> Gems?",
"untilNoFace": "Until we add Facebook, use your UUID and API Token to log in (found at https://habitrpg.com > Options > Settings).",
"noReachServer": "Server not currently reachable, try again later",
"errorUpCase": "ERROR:",
"newPassSent": "New password sent.",

View File

@@ -39,6 +39,8 @@
"editGroup": "Edit Group",
"newGroupName": "<%= groupType %> Name",
"groupName": "Group Name",
"groupLeader": "Group Leader",
"groupID": "Group ID",
"groupDescr": "Description shown in public Guilds list (Markdown OK)",
"logoUrl": "Logo URL",
"assignLeader": "Assign Group Leader",

View File

@@ -3,7 +3,7 @@
"quest": "quest",
"completed": "Completed!",
"youReceived": "You've Received",
"questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Options > Social > Party.",
"questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Social > Party.",
"inviteParty": "Invite Party",
"questInvitation": "Quest Invitation: ",
"questInvitationTitle": "Quest Invitation",

View File

@@ -36,6 +36,11 @@
"newDailyBulk": "New Dailies (one per line)",
"streakCounter": "Streak Counter",
"repeat": "Repeat",
"repeatEvery": "Repeat Every",
"repeatDays": "Every X Days",
"repeatWeek": "On Certain Days of the Week",
"day": "Day",
"days": "Days",
"restoreStreak": "Restore Streak",
"todos": "To-Dos",
"newTodo": "New To-Do",
@@ -60,6 +65,9 @@
"clearTags": "Clear",
"hideTags": "Hide",
"showTags": "Show",
"startDate": "Start Date",
"startDateHelpTitle": "When should this task start?",
"startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
"streakName": "Streak Achievements",
"streakText": "Has performed <%= streaks %> 21-day streaks on Dailies",
"streakSingular": "Streaker",
@@ -81,17 +89,17 @@
"dailiesRestingInInn": "You're Resting in the Inn! Your Dailies will NOT hurt you tonight, but they WILL still refresh every day. If you're in a quest, you won't deal damage/collect items until you check out of the Inn, but you can still be injured by a Boss if your Party mates skip their own Dailies.",
"habitHelp1": "Good Habits are things that you do often. They award Gold and Experience every time you click the <%= plusIcon %>.",
"habitHelp2": "Bad Habits are things you want to avoid doing. They remove Health every time you click the <%= minusIcon %>.",
"habitHelp3": "For inspiration, check out <%= linkStart %>these sample Habits<%= linkEnd %>.",
"habitHelp3": "For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_Habits' target='_blank'>sample Habits</a>!",
"newbieGuild": "More questions? Ask in the <%= linkStart %>Newbies Guild<%= linkEnd %>!",
"dailyHelp1": "Dailies repeat <%= emphasisStart %>every day<%= emphasisEnd %> that they are active. Click the <%= pencilIcon %> to change the days a Daily is active.",
"dailyHelp2": "If you don't complete active Dailies, you lose Health when your day rolls over.",
"dailyHelp3": "Dailies turn <%= emphasisStart %>redder<%= emphasisEnd %> when you miss them, and <%= emphasisStart %>bluer<%= emphasisEnd %> when you complete them. The redder the Daily, the more it will reward you... or hurt you.",
"dailyHelp4": "To change when your day rolls over, go to <%= linkStart %> Settings > Site<%= linkEnd %> > Custom Day Start.",
"dailyHelp5": "For inspiration, check out these <%= linkStart %>sample Dailies<%= linkEnd %>!",
"dailyHelp5": "For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_Dailies' target='_blank'>sample Dailies</a>!",
"toDoHelp1": "To-Dos start yellow, and get redder (more valuable) the longer it takes to complete them.",
"toDoHelp2": "To-Dos never hurt you! They only award Gold and Experience.",
"toDoHelp3": "Breaking a To-Do down into a checklist of smaller items will make it less scary, and will increase your points!",
"toDoHelp4": "For inspiration, check out these <%= linkStart %>sample To-Dos<%= linkEnd %>!",
"toDoHelp4": "For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_To-Dos' target='_blank'>sample To-Dos</a>!",
"rewardHelp1": "The Equipment you buy for your avatar is stored in <%= linkStart %>Inventory > Equipment<%= linkEnd %>.",
"rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here during World Events.",

View File

@@ -1877,9 +1877,9 @@ _.each api.subscriptionBlocks, (b,k)->b.key = k
repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true}
api.userDefaults =
habits: [
{type: 'habit', text: t('defaultHabit1Text'), notes: t('defaultTaskNotes'), value: 0, up: true, down: false, attribute: 'per' }
{type: 'habit', text: t('defaultHabit2Text'), notes: t('defaultTaskNotes'), value: 0, up: false, down: true, attribute: 'str'}
{type: 'habit', text: t('defaultHabit3Text'), notes: t('defaultTaskNotes'), value: 0, up: true, down: true, attribute: 'str'}
{type: 'habit', text: t('defaultHabit1Text'), value: 0, up: true, down: false, attribute: 'per' }
{type: 'habit', text: t('defaultHabit2Text'), value: 0, up: false, down: true, attribute: 'str'}
{type: 'habit', text: t('defaultHabit3Text'), value: 0, up: true, down: true, attribute: 'str'}
]
dailys: [
@@ -1898,7 +1898,7 @@ api.userDefaults =
]
rewards: [
{type: 'reward', text: t('defaultReward1Text'), notes: t('defaultTaskNotes'), value: 10 }
{type: 'reward', text: t('defaultReward1Text'), value: 10 }
# {type: 'reward', text: t('defaultReward2Text'), notes: t('defaultReward2Notes'), value: 10 }
]

View File

@@ -72,13 +72,37 @@ api.daysSince = (yesterday, options = {}) ->
Math.abs api.startOfDay(_.defaults {now:yesterday}, o).diff(api.startOfDay(_.defaults {now:o.now}, o), 'days')
###
Should the user do this taks on this date, given the task's repeat options and user.preferences.dayStart?
Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart?
###
api.shouldDo = (day, repeat, options={}) ->
return false unless repeat
api.shouldDo = (day, dailyTask, options = {}) ->
return false unless dailyTask.type == 'daily' && dailyTask.repeat
if !dailyTask.startDate
dailyTask.startDate = moment().toDate()
if dailyTask.startDate instanceof String
dailyTask.startDate = moment(dailyTask.startDate).toDate()
o = sanitizeOptions options
selected = repeat[api.dayMapping[api.startOfDay(_.defaults {now:day}, o).day()]]
return selected
day = api.startOfDay(_.defaults {now:day}, o)
dayOfWeekNum = day.day() # e.g. 1 for Monday if week starts on Mon
# check if event is today or in the future
hasStartedCheck = day >= api.startOfDay(_.defaults {now:dailyTask.startDate}, o)
if dailyTask.frequency == 'daily'
daysSinceTaskStart = api.numDaysApart(day.startOf('day'), dailyTask.startDate, o)
everyXCheck = (daysSinceTaskStart % dailyTask.everyX == 0)
return everyXCheck && hasStartedCheck
else if dailyTask.frequency == 'weekly'
dayOfWeekCheck = dailyTask.repeat[api.dayMapping[dayOfWeekNum]]
return dayOfWeekCheck && hasStartedCheck
else
# unexpected frequency string
return false
api.numDaysApart = (day1, day2, o) ->
startOfDay1 = api.startOfDay(_.defaults {now:day1}, o)
startOfDay2 = api.startOfDay(_.defaults {now:day2}, o)
numDays = Math.abs(startOfDay1.diff(startOfDay2, 'days'))
return numDays
###
------------------------------------------------------
@@ -211,7 +235,7 @@ api.taskDefaults = (task={}) ->
_.defaults(task, {up:true,down:true}) if task.type is 'habit'
_.defaults(task, {history: []}) if task.type in ['habit', 'daily']
_.defaults(task, {completed:false}) if task.type in ['daily', 'todo']
_.defaults(task, {streak:0, repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}}) if task.type is 'daily'
_.defaults(task, {streak:0, repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}}, startDate: new Date(), everyX: 1, frequency: 'weekly') if task.type is 'daily'
task._id = task.id # may need this for TaskSchema if we go back to using it, see http://goo.gl/a5irq4
task.value ?= if task.type is 'reward' then 10 else 0
task.priority = 1 unless _.isNumber(task.priority) # hotfix for apiv1. once we're off apiv1, we can remove this
@@ -281,7 +305,7 @@ api.taskClasses = (task, filters=[], dayStart=0, lastCron=+new Date, showComplet
# show as completed if completed (naturally) or not required for today
if type in ['todo', 'daily']
if completed or (type is 'daily' and !api.shouldDo(+new Date, task.repeat, {dayStart}))
if completed or (type is 'daily' and !api.shouldDo(+new Date, task, {dayStart}))
classes += " completed"
else
classes += " uncompleted"
@@ -1565,7 +1589,7 @@ api.wrap = (user, main=true) ->
{completed, repeat} = daily
thatDay = moment(now).subtract({days: 1})
if api.shouldDo(thatDay, repeat, user.preferences) || completed
if api.shouldDo(thatDay.toDate(), daily, user.preferences) || completed
_.each daily.checklist, ((box)->box.completed=false;true)
daily.completed = false
return
@@ -1592,7 +1616,7 @@ api.wrap = (user, main=true) ->
scheduleMisses = 0
_.times daysMissed, (n) ->
thatDay = moment(now).subtract({days: n + 1})
if api.shouldDo(thatDay, repeat, user.preferences)
if api.shouldDo(thatDay.toDate(), task, user.preferences)
scheduleMisses++
if user.stats.buffs.stealth
user.stats.buffs.stealth--

View File

@@ -6,16 +6,16 @@ Group = require("../../website/src/models/group").model
app = require("../../website/src/server")
describe "Guilds", ->
before (done) ->
registerNewUser ->
User.findByIdAndUpdate user._id,
$set:
"balance": 10
, (err, _user) ->
done()
, true
context "creating groups", ->
before (done) ->
registerNewUser ->
User.findByIdAndUpdate user._id,
$set:
"balance": 10
, (err, _user) ->
done()
, true
it "can create a public guild", (done) ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
@@ -53,19 +53,42 @@ describe "Guilds", ->
done()
, false
context "finding groups", ->
it "can find a guild", (done) ->
guild = undefined
context "get guilds", ->
guild = undefined
beforeEach (done)->
request.post(baseURL + "/groups").send(
name: "TestGroup2"
type: "guild"
).end (res) ->
guild = res.body
request.get(baseURL + "/groups/" + guild._id)
.send()
done()
it "can find a guild", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expectCode res, 200
expect(guild._id).to.equal res.body._id
expect(res.body._id).to.equal res.body._id
done()
it "transforms members array to an arrray of user objects", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expectCode res, 200
members = res.body.members
# @TODO: would be more instructive if it had more members in guild :(
_(members).each (member) ->
expect(member).to.be.an 'object'
expect(member.profile.name).to.exist
done()
it "transforms leader id to a user object", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expectCode res, 200
leader = res.body.leader
expect(leader).to.be.an 'object'
expect(leader.profile.name).to.exist
done()
it "can list guilds", (done) ->
@@ -228,38 +251,46 @@ describe "Guilds", ->
describe "Public Guilds", ->
guild = undefined
before (done) ->
request.post(baseURL + "/groups").send(
name: "TestPublicGroup"
type: "guild"
privacy: "public"
).end (res) ->
expectCode res, 200
guild = res.body
expect(guild.members.length).to.equal 1
expect(guild.leader).to.equal user._id
#Add members to guild
async.waterfall [
(cb) ->
registerManyUsers 15, cb
async.waterfall [
(cb) ->
registerNewUser ->
User.findByIdAndUpdate user._id, {$set: { "balance": 10 } }, (err, _user) ->
cb()
, true
(cb) ->
request.post(baseURL + "/groups").send(
name: "TestPublicGroup"
type: "guild"
privacy: "public"
).end (res) ->
guild = res.body
expect(guild.members.length).to.equal 1
expect(guild.leader).to.equal user._id
#Add members to guild
cb()
(_members, cb) ->
members = _members
(cb) ->
registerManyUsers 15, cb
joinGuild = (member, callback) ->
request.post(baseURL + "/groups/" + guild._id + "/join")
.set("X-API-User", member._id)
.set("X-API-Key", member.apiToken)
.end ->
callback(null, null)
(_members, cb) ->
members = _members
async.map members, joinGuild, (err, results) -> cb()
], done
joinGuild = (member, callback) ->
request.post(baseURL + "/groups/" + guild._id + "/join")
.set("X-API-User", member._id)
.set("X-API-Key", member.apiToken)
.end ->
callback(null, null)
async.map members, joinGuild, (err, results) -> cb()
], done
context "is a member", ->
before (done) ->
registerNewUser ->
request.post(baseURL + "/groups/" + guild._id + "/join")
.end ->
.end (res)->
done()
, true

View File

@@ -159,7 +159,7 @@ describe 'User', ->
it 'handles perfect days', ->
user = newUser()
user.dailys = []
_.times 3, ->user.dailys.push shared.taskDefaults({type:'daily'})
_.times 3, ->user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')})
cron = -> user.lastCron = moment().subtract(1,'days');user.fns.cron()
cron()
@@ -193,7 +193,7 @@ describe 'User', ->
user.preferences.sleep = true
cron = -> user.lastCron = moment().subtract(1, 'days');user.fns.cron()
user.dailys = []
_.times 2, -> user.dailys.push shared.taskDefaults({type:'daily'})
_.times 2, -> user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')})
it 'remains in the inn on cron', ->
cron()
@@ -883,8 +883,9 @@ describe 'Cron', ->
before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat
before.dailys[0].streak = after.dailys[0].streak = 10
before.dailys[0].completed = after.dailys[0].completed = true if options.checked
before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days')
if options.shouldDo
expect(shared.shouldDo(now, options.repeat, {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
expect(shared.shouldDo(now.toDate(), after.dailys[0], {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
after.fns.cron {now}
before.stats.mp=after.stats.mp #FIXME
switch options.expect

330
test/common/dailies.coffee Normal file
View File

@@ -0,0 +1,330 @@
_ = require 'lodash'
expect = require 'expect.js'
sinon = require 'sinon'
moment = require 'moment'
shared = require '../../common/script/index.coffee'
shared.i18n.translations = require('../../website/src/i18n.js').translations
repeatWithoutLastWeekday = ()->
repeat = {su:1,m:1,t:1,w:1,th:1,f:1,s:1}
if shared.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday
repeat.su = false
else
repeat.s = false
{repeat: repeat}
### Helper Functions ####
# @TODO: Refactor into helper file
newUser = (addTasks=true)->
buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false}
user =
auth:
timestamps: {}
stats: {str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs}
items:
lastDrop:
count: 0
hatchingPotions: {}
eggs: {}
food: {}
gear:
equipped: {}
costume: {}
party:
quest:
progress:
down: 0
preferences: {}
dailys: []
todos: []
rewards: []
flags: {}
achievements: {}
contributor:
level: 2
shared.wrap(user)
user.ops.reset(null, ->)
if addTasks
_.each ['habit', 'todo', 'daily'], (task)->
user.ops.addTask {body: {type: task, id: shared.uuid()}}
user
cron = (usr) ->
usr.lastCron = moment().subtract(1,'days')
usr.fns.cron()
describe 'daily/weekly that repeats everyday (default)', ->
user = null
daily = null
weekly = null
describe 'when startDate is in the future', ->
beforeEach ->
user = newUser()
user.dailys = [
shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'daily'})
shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'weekly', repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}})
]
daily = user.dailys[0]
weekly = user.dailys[1]
it 'does not damage user for not completing it', ->
cron(user)
expect(user.stats.hp).to.be 50
it 'does not change value on cron if daily is incomplete', ->
cron(user)
expect(daily.value).to.be 0
expect(weekly.value).to.be 0
it 'does not reset checklists if daily is not marked as complete', ->
checklist = [
{
'text' : '1',
'id' : 'checklist-one',
'completed' : true
},
{
'text' : '2',
'id' : 'checklist-two',
'completed' : true
},
{
'text' : '3',
'id' : 'checklist-three',
'completed' : false
}
]
daily.checklist = checklist
weekly.checklist = checklist
cron(user)
expect(daily.checklist[0].completed).to.be true
expect(daily.checklist[1].completed).to.be true
expect(daily.checklist[2].completed).to.be false
expect(weekly.checklist[0].completed).to.be true
expect(weekly.checklist[1].completed).to.be true
expect(weekly.checklist[2].completed).to.be false
it 'resets checklists if daily is marked as complete', ->
checklist = [
{
'text' : '1',
'id' : 'checklist-one',
'completed' : true
},
{
'text' : '2',
'id' : 'checklist-two',
'completed' : true
},
{
'text' : '3',
'id' : 'checklist-three',
'completed' : false
}
]
daily.checklist = checklist
weekly.checklist = checklist
daily.completed = true
weekly.completed = true
cron(user)
_.each daily.checklist, (box)->
expect(box.completed).to.be false
_.each weekly.checklist, (box)->
expect(box.completed).to.be false
it 'is due on startDate', ->
daily_due_today = shared.shouldDo moment(), daily
daily_due_on_start_date = shared.shouldDo moment().add(7, 'days'), daily
expect(daily_due_today).to.be false
expect(daily_due_on_start_date).to.be true
weekly_due_today = shared.shouldDo moment(), weekly
weekly_due_on_start_date = shared.shouldDo moment().add(7, 'days'), weekly
expect(weekly_due_today).to.be false
expect(weekly_due_on_start_date).to.be true
describe 'when startDate is in the past', ->
completeDaily = null
beforeEach ->
user = newUser()
user.dailys = [
shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'daily'})
shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'weekly'})
]
daily = user.dailys[0]
weekly = user.dailys[1]
it 'does damage user for not completing it', ->
cron(user)
expect(user.stats.hp).to.be.lessThan 50
it 'decreases value on cron if daily is incomplete', ->
cron(user)
expect(daily.value).to.be.lessThan 0
expect(weekly.value).to.be.lessThan 0
it 'resets checklists if daily is not marked as complete', ->
checklist = [
{
'text' : '1',
'id' : 'checklist-one',
'completed' : true
},
{
'text' : '2',
'id' : 'checklist-two',
'completed' : true
},
{
'text' : '3',
'id' : 'checklist-three',
'completed' : false
}
]
daily.checklist = checklist
weekly.checklist = checklist
cron(user)
_.each daily.checklist, (box)->
expect(box.completed).to.be false
_.each weekly.checklist, (box)->
expect(box.completed).to.be false
it 'resets checklists if daily is marked as complete', ->
checklist = [
{
'text' : '1',
'id' : 'checklist-one',
'completed' : true
},
{
'text' : '2',
'id' : 'checklist-two',
'completed' : true
},
{
'text' : '3',
'id' : 'checklist-three',
'completed' : false
}
]
daily.checklist = checklist
daily.completed = true
weekly.checklist = checklist
weekly.completed = true
cron(user)
_.each daily.checklist, (box)->
expect(box.completed).to.be false
_.each weekly.checklist, (box)->
expect(box.completed).to.be false
describe 'when startDate is today', ->
completeDaily = null
beforeEach ->
user = newUser()
user.dailys = [
# Must set start date to yesterday, because cron mock sets last cron to yesterday
shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'daily'})
shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'weekly'})
]
daily = user.dailys[0]
weekly = user.dailys[1]
it 'does damage user for not completing it', ->
cron(user)
expect(user.stats.hp).to.be.lessThan 50
it 'decreases value on cron if daily is incomplete', ->
cron(user)
expect(daily.value).to.be.lessThan 0
expect(weekly.value).to.be.lessThan 0
it 'resets checklists if daily is not marked as complete', ->
checklist = [
{
'text' : '1',
'id' : 'checklist-one',
'completed' : true
},
{
'text' : '2',
'id' : 'checklist-two',
'completed' : true
},
{
'text' : '3',
'id' : 'checklist-three',
'completed' : false
}
]
daily.checklist = checklist
weekly.checklist = checklist
cron(user)
_.each daily.checklist, (box)->
expect(box.completed).to.be false
_.each weekly.checklist, (box)->
expect(box.completed).to.be false
it 'resets checklists if daily is marked as complete', ->
checklist = [
{
'text' : '1',
'id' : 'checklist-one',
'completed' : true
},
{
'text' : '2',
'id' : 'checklist-two',
'completed' : true
},
{
'text' : '3',
'id' : 'checklist-three',
'completed' : false
}
]
daily.checklist = checklist
daily.completed = true
weekly.checklist = checklist
weekly.completed = true
cron(user)
_.each daily.checklist, (box)->
expect(box.completed).to.be false
_.each weekly.checklist, (box)->
expect(box.completed).to.be false
describe 'daily that repeats every x days', ->
user = null
daily = null
beforeEach ->
user = newUser()
user.dailys = [ shared.taskDefaults({type:'daily', startDate: moment(), frequency: 'daily'}) ]
daily = user.dailys[0]
_.times 11, (due) ->
it 'where x equals ' + due, ->
daily.everyX = due
_.times 30, (day) ->
isDue = shared.shouldDo moment().add(day, 'days'), daily
expect(isDue).to.be true if day % due == 0
expect(isDue).to.be false if day % due != 0

View File

@@ -21,7 +21,7 @@
padding: 1em;
margin-bottom: 0.5em;
.formatting-help
.slight-vertical-padding
clear: both
padding-top: 0.618em

View File

@@ -12,7 +12,7 @@ for $stage in $stages
.color-{$stage[0]}:not(.completed)
background-color: $stage[1]
border: 1px solid shade($stage[1],10%)
.priority-multiplier, .task-attributes, .repeat-days
.priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
li
hrpg-button-color-mixin($stage[1])
button
@@ -63,7 +63,7 @@ for $stage in $stages
color: darken($completed,30%)
background-color: $completed
border: 1px solid shade($completed,10%)
.priority-multiplier, .task-attributes, .repeat-days
.priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
li
hrpg-button-color-mixin($completed)
button
@@ -410,6 +410,10 @@ form
padding: 0 0 1em
margin-bottom: 1em
button.advanced-options-toggle
display: block;
width: 100%;
background: none;
.option-title
font-size: 1em
margin: 0 0 0.5em
@@ -507,7 +511,7 @@ form
form
padding-bottom: 1em
.priority-multiplier, .task-attributes, .repeat-days
.priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
text-align: center
li
@extend $hrpg-button
@@ -517,6 +521,7 @@ form
&:last-of-type
margin-right: 0
.repeat-days
padding-bottom: 1em
li
button
min-width: 2.5em
@@ -524,6 +529,11 @@ form
text-align: center
@extend $hrpg-button
// Dailies
.dailies
.repeat-weekly
padding-bottom: 1em
// Habits task button styles (+ -)
.habits
.task-actions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -4,6 +4,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide) {
$scope.obj = User.user; // used for task-lists
$scope.user = User.user;
$scope.armoireCount = function(gear) {
return Shared.countArmoire(gear);
};
@@ -131,6 +132,19 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
*/
$scope._today = moment().add({days: 1});
/*
------------------------
Dailies
------------------------
*/
$scope.openDatePicker = function($event, task) {
$event.preventDefault();
$event.stopPropagation();
task._isDatePickerOpen = !task._isDatePickerOpen;
}
/*
------------------------
Checklists
@@ -216,7 +230,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
$scope.shouldShow = function(task, list, prefs){
if (task._editing) // never hide a task while being edited
return true;
var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task.repeat, prefs) : true;
var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task, prefs) : true;
switch (list.view) {
case "yellowred": // Habits
return task.value < 1;

View File

@@ -40,6 +40,7 @@ var populateQuery = function(type, q, additionalFields){
q.populate('members', partyFields + (additionalFields ? (' ' + additionalFields) : ''));
else
q.populate(guildPopulate);
q.populate('leader', nameFields);
q.populate('invites', nameFields);
q.populate({
path: 'challenges',

View File

@@ -8,6 +8,7 @@ var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var shared = require('../../../common');
var _ = require('lodash');
var moment = require('moment');
// Task Schema
// -----------
@@ -50,10 +51,13 @@ var checklist = [{
var DailySchema = new Schema(
_.defaults({
type: {type:String, 'default': 'daily'},
type: {type: String, 'default': 'daily'},
frequency: {type: String, 'default': 'weekly', enum: ['daily', 'weekly']},
everyX: {type: Number, 'default': 1}, // e.g. once every X weeks
startDate: {type: Date, 'default': moment().startOf('day').toDate()},
history: Array,
completed: {type: Boolean, 'default': false},
repeat: {
repeat: { // used only for 'weekly' frequency,
m: {type: Boolean, 'default': true},
t: {type: Boolean, 'default': true},
w: {type: Boolean, 'default': true},

View File

@@ -459,7 +459,9 @@ UserSchema.pre('save', function(next) {
newTask.name = newTask.name(self.preferences.language);
}else{
newTask.text = newTask.text(self.preferences.language);
newTask.notes = newTask.notes(self.preferences.language);
if(newTask.notes) {
newTask.notes = newTask.notes(self.preferences.language);
}
if(newTask.checklist){
newTask.checklist = _.map(newTask.checklist, function(checklistItem){

View File

@@ -179,8 +179,11 @@ module.exports.setupConfig = function(){
baseUrl = nconf.get('BASE_URL');
module.exports.ga = require('universal-analytics')(nconf.get('GA_ID'));
var mixpanel = require('mixpanel')
module.exports.mixpanel = mixpanel.init(nconf.get('MP_ID'));
var mixpanel = isProd && require('mixpanel');
module.exports.mixpanel = mixpanel
? mixpanel.init(nconf.get('MP_ID'))
: { track: function() {} };
};
var algorithm = 'aes-256-ctr';

View File

@@ -38,7 +38,7 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a
include ./shared/header/menu
include ./shared/modals/index
include ./shared/header/header
include ./shared/tasks/lists
include ./shared/tasks/index
include ./main/index
include ./options/index

View File

@@ -22,7 +22,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
span.glyphicon.glyphicon-ban-circle
=env.t('leave')
a.btn.btn-success.pull-right(ng-if=':: !isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join')
span(ng-if='group.leader == user.id')
span(ng-if='group.leader._id == user.id')
button.btn.btn-sm.btn-primary.pull-right(ng-click='save(group)', ng-show='group._editing')=env.t('save')
button.btn.btn-sm.btn-default.pull-right(ng-click='group._editing = true', ng-hide='group._editing')=env.t('editGroup')
@@ -51,7 +51,13 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
img.pull-right(ng-show='group.logo', ng-src='{{group.logo}}')
markdown(text='group.description')
hr
small.muted Group ID: {{group._id}}
p=env.t('groupLeader')
|:
a(class='badge badge-info', ng-click='clickMember(group.leader._id, true)')
| {{group.leader.profile.name}}
.slight-vertical-padding
small.muted=env.t('groupID')
|: {{group._id}}
include ./challenge-box
@@ -78,14 +84,14 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
ng-change='set({"party.orderAscending": user.party.orderAscending})'
)
table.table.table-striped(bindonce='group')
tr(ng-repeat='member in group.members track by member._id')
tr(ng-repeat='member in group.members track by member._id' ng-if='member._id != group.leader._id')
td.media
// allow leaders to ban members
div.pull-left(ng-show='group.leader == user.id && user.id!=member._id')
div.pull-left(ng-show='group.leader._id == user.id')
a.media-object(ng-click='removeMember(group, member, true)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a.media-body
span(ng-class='{"badge badge-info": group.leader==member._id}', ng-click='clickMember(member._id, true)')
span(ng-click='clickMember(member._id, true)')
| {{member.profile.name}}
tr(ng-if='group.memberCount > group.members.length')
td
@@ -98,7 +104,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
tr(ng-repeat='invite in group.invites')
td.media
// allow leaders to ban members
div.pull-left(ng-show='group.leader == user.id')
div.pull-left(ng-show='group.leader._id == user.id')
a.media-object(ng-click='removeMember(group, invite, false)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a.media-body
@@ -117,7 +123,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
td
.popover.static-popover.fade.right.in.wide-popover
.arrow
h3.popover-title {{Members.members[group.leader].profile.name}}
h3.popover-title {{group.leader.profile.name}}
.popover-content
markdown(text='group.leaderMessage')
div(ng-controller='ChatCtrl')

View File

@@ -1,7 +1,7 @@
small.btn-link(ng-init='showHelp = false', ng-click='showHelp = !showHelp')
| {{showHelp ? env.t('hideFormattingHelp') : env.t('showFormattingHelp')}}
.formatting-help(ng-if='showHelp')
.slight-vertical-padding(ng-if='showHelp')
table.table.table-striped
tr.info
td.col-xs-6 #[b=env.t('youType')]

View File

@@ -67,6 +67,7 @@ script(type='text/ng-template', id='modals/chooseClass.html')
.well(ng-show='selectedClass=="healer"')=env.t('healerText')
.modal-footer
button.btn.btn-danger(ng-click='user.ops.disableClasses({}); $close()', popover-placement='top', popover-trigger='mouseenter', popover=env.t('optOutText'))=env.t('optOut')
span(popover-placement='left', popover-trigger='mouseenter', popover=env.t('optOutText'))
button.btn.btn-danger(ng-click='user.ops.disableClasses({}); $close()')=env.t('optOut')
button.btn.btn-primary(ng-disabled='!selectedClass' ng-click='changeClass(selectedClass); $close()')=env.t('select')
.pull-left!=env.t('chooseClassLearn')

View File

@@ -1,33 +1,65 @@
h5 6/1/2015 - NEW EQUIPMENT: THE ENCHANTED ARMOIRE, JUNE BACKGROUNDS, AND NEW MOUNT POSITIONING!
h5 6/11/2015 - REPEATING TASKS, START DATE, AND MOBILE APP UPDATES!
p
br
p.small.muted by Blade and fallenpanda
hr
tr
td
.promo_enchanted_armoire.pull-right
h5 New Equipment: The Enchanted Armoire!
p Now after you achieve Ultimate Gear, you'll unlock a new Reward: THE ENCHANTED ARMOIRE!
h5 New Repeat Option for Dailies
p Dailies now have a new Advanced Option: Repeat Every X Days. You've wanted this feature for a long time, and it's finally here!
br
p Click on the Enchanted Armoire, a 100 GP Reward in the Rewards Column, for a random chance at special Equipment! It may also give you random XP or food items. We'll be adding new equipment to it every month, but even when you've exhausted the current supply, you can keep clicking for a chance at food and XP.
p First, please note that this new option is OPT-IN only. We won't make any changes to your preexisting Dailies without you knowing it. We wouldn't do that!
br
p Now go spend all that accumulated Gold! May the Random Number Generator smile upon you...
p.small.muted by Lemoness and SabreCat
p.small.muted Art by Kiwibot, Starsystemic, UncommonCriminal, Zoebeagle, and Andrews38
p That being said, here are the new features:
tr
td
.background_island_waterfalls.pull-right
h5 June Backgrounds Revealed
p There are three new avatar backgrounds in the <a href='/#/options/profile/backgrounds' target='_blank'>Background Shop</a>! Now your avatar can paddle a Drifting Raft, float through a sea of Shimmery Bubbles, or picnic near Island Waterfalls!
p.small.muted by (in order): Teto is Great, beffymaroo, and UncommonCriminal
h5 Repeating Tasks
p Use the "Every X Days" function under Dailies Advanced Options to create tasks that repeat after a certain number of days have passed, whether every 2 days, every 15 days, every 30 days... You choose the number that works for you!
br
p These Dailies are only due on those given dates. Need to pay your rent every 30 days? Take medicine every other day? Water your plants every 4 days? No longer a problem.
tr
td
h5 New Mount Positioning!
p The mount positioning has been fixed for all the base mounts where it looked like the avatar was riding extreme sidesaddle. Now avatars sit properly, no longer clinging to the sides of their mounts for dear life.
p.small.muted by Kiwibot, Lemoness, and SabreCat
h5 Start Date
p Dailies now have a Start Date. They will not be due before this date. This means that if you want to add a new Daily while you're thinking about it, but not have it be due until later, you can achieve that by setting a future Start Date!
tr
td
h5 Mobile App Updates
p New <a href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg' target='_blank'>Android</a> and <a href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8' target='_blank'>iOS</a> updates are available to support this feature. Please, update your apps before using it, or the new repeating Dailies will not display normally on the mobile apps!
tr
td
h5 Other Notes
p For a short period of time, the <a href='http://data.habitrpg.com' target='_blank'>Data Display Tool</a> will not be able to calculate damage correctly for Repeat Every X Dailies. We'll get that updated very soon so that it will be accurate again!
br
p If you still have questions about Repeat Every X Dailies, don't hesitate to ask in <a href='/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild</a>!
hr
a(href='/static/old-news', target='_blank') Read older news
mixin oldNews
h5 6/1/2015 - NEW EQUIPMENT: THE ENCHANTED ARMOIRE, JUNE BACKGROUNDS, AND NEW MOUNT POSITIONING!
tr
td
.promo_enchanted_armoire.pull-right
h5 New Equipment: The Enchanted Armoire!
p Now after you achieve Ultimate Gear, you'll unlock a new Reward: THE ENCHANTED ARMOIRE!
br
p Click on the Enchanted Armoire, a 100 GP Reward in the Rewards Column, for a random chance at special Equipment! It may also give you random XP or food items. We'll be adding new equipment to it every month, but even when you've exhausted the current supply, you can keep clicking for a chance at food and XP.
br
p Now go spend all that accumulated Gold! May the Random Number Generator smile upon you...
p.small.muted by Lemoness and SabreCat
p.small.muted Art by Kiwibot, Starsystemic, UncommonCriminal, Zoebeagle, and Andrews38
tr
td
.background_island_waterfalls.pull-right
h5 June Backgrounds Revealed
p There are three new avatar backgrounds in the <a href='/#/options/profile/backgrounds' target='_blank'>Background Shop</a>! Now your avatar can paddle a Drifting Raft, float through a sea of Shimmery Bubbles, or picnic near Island Waterfalls!
p.small.muted by (in order): Teto is Great, beffymaroo, and UncommonCriminal
tr
td
h5 New Mount Positioning!
p The mount positioning has been fixed for all the base mounts where it looked like the avatar was riding extreme sidesaddle. Now avatars sit properly, no longer clinging to the sides of their mounts for dear life.
p.small.muted by Kiwibot, Lemoness, and SabreCat
h5 6/1/2015 - JUNE MYSTERY ITEM!
tr
td

View File

@@ -0,0 +1,70 @@
div(ng-if='::task.type!="reward"')
button.advanced-options-toggle.option-title.mega(type='button',
ng-click='task._advanced = !task._advanced', tooltip=env.t('expandCollapse'))
=env.t('advancedOptions')
div(ng-show='task._advanced')
div(ng-if='::task.type == "daily"')
.form-group
legend.option-title
span.hint(popover-title=env.t('startDateHelpTitle'), popover=env.t("startDateHelp"), popover-trigger='mouseenter')
=env.t('startDate')
input.form-control(type='text', ng-model='task.startDate',
datepicker-popup='{{::user.preferences.dateFormat}}', is-open='datepickerOpened',
ng-click='datepickerOpened = true')
hr
.form-group
legend.option-title=env.t('repeat')
select.form-control(ng-model='task.frequency')
option(value='weekly')=env.t('repeatWeek')
option(value='daily')=env.t('repeatDays')
include ./dailies/repeat_options
hr
fieldset.option-group.advanced-option(ng-show="task._advanced")
legend.option-title
a.hint.priority-multiplier-help(href='http://habitrpg.wikia.com/wiki/Difficulty', target='_blank', popover-title=env.t('difficultyHelpTitle'), popover-trigger='mouseenter', popover=env.t('difficultyHelpContent'))=env.t('difficulty')
ul.priority-multiplier
li
button(type='button', ng-class='{active: task.priority==1 || !task.priority}',
ng-click='task.challenge.id || (task.priority=1)')
=env.t('easy')
li
button(type='button', ng-class='{active: task.priority==1.5}',
ng-click='task.challenge.id || (task.priority=1.5)')
=env.t('medium')
li
button(type='button', ng-class='{active: task.priority==2}',
ng-click='task.challenge.id || (task.priority=2)')
=env.t('hard')
span(ng-if='task.type=="daily"')
legend.option-title.pull-left=env.t('restoreStreak')
input.option-content(type='number', ng-model='task.streak')
div(ng-if='::(user.preferences.allocationMode == "taskbased" && user.preferences.automaticAllocation) || $state.is("options.social.challenges")')
legend.option-title.pull-left=env.t('attributes')
ul.task-attributes
li
button(type='button', ng-class='{active: task.attribute=="str"}',
ng-click='task.attribute="str"')
=env.t('physical')
li
button(type='button', ng-class='{active: task.attribute=="int"}',
ng-click='task.attribute="int"')
=env.t('mental')
li
button(type='button', ng-class='{active: task.attribute=="con"}',
ng-click='task.attribute="con"')
=env.t('social')
li
button(type='button', ng-class='{active: task.attribute=="per"}',
ng-click='task.attribute="per"',
popover=env.t('otherExamples'), popover-trigger='mouseenter', popover-placement='top')
=env.t('other')

View File

@@ -0,0 +1,22 @@
.task-checklist-edit(ng-if='!$state.includes("options.social.challenges")')
ul
li
button(type='button', ng-if='!task.checklist[0] && (task.type=="daily" || task.type=="todo")',
ng-click='addChecklist(task)')
span.glyphicon.glyphicon-tasks
span=env.t('addChecklist')
form.checklist-form(ng-if='task.checklist')
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
legend.option-title
span.hint(popover=env.t('checklistText'), popover-trigger='mouseenter', popover-placement='bottom')
=env.t('checklist')
ul(hrpg-sort-checklist)
li(ng-repeat='item in task.checklist')
//input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
//-,ng-blur='saveTask(task,true)')
span.checklist-icon.glyphicon.glyphicon-resize-vertical
input(type='text', ng-model='item.text',
ui-keyup="{'13':'addChecklistItem(task,$event,$index)','38 40':'navigateChecklist(task,$index,$event)'}")
a(ng-click='removeChecklistItem(task,$event,$index,true)')
span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))

View File

@@ -0,0 +1,3 @@
fieldset.option-group.calendar(ng-if='::task.type=="daily"', class="option-group")
.dailies
include ./repeat_options

View File

@@ -0,0 +1,26 @@
legend.option-title=env.t('repeatEvery')
// If frequency is daily
ng-form.form-group(name='everyX' ng-if='task.frequency=="daily"')
.input-group
input.form-control(type='number', ng-model='task.everyX', min='0', required)
span.input-group-addon {{task.everyX == 1 ? env.t('day') : env.t('days')}}
// If frequency is weekly
.form-group(ng-if='task.frequency=="weekly"')
ul.repeat-days
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
li
button(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)') {{::moment.weekdaysMin(0)}}
li
button(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)') {{::moment.weekdaysMin(1)}}
li
button(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)') {{::moment.weekdaysMin(2)}}
li
button(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)') {{::moment.weekdaysMin(3)}}
li
button(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)') {{::moment.weekdaysMin(4)}}
li
button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
li
button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}

View File

@@ -0,0 +1,8 @@
fieldset.option-group.plusminus(ng-if='task.type=="habit" && !task.challenge.id')
legend.option-title=env.t('direction/Actions')
span.task-checker
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
label(for='{{obj._id}}_{{task.id}}-option-plus')
span.task-checker
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
label(for='{{obj._id}}_{{task.id}}-option-minus')

View File

@@ -0,0 +1,52 @@
div(ng-if='task._editing')
.task-options
// Broken Challenge
.well(ng-if='task.challenge.broken')
div(ng-if='task.challenge.broken=="TASK_DELETED"')
p=env.t('brokenTask')
p
a(ng-click='unlink(task, "keep")')=env.t('keepIt')
| &nbsp;&nbsp;
a(ng-click="removeTask(task, obj[list.type+'s'])")=env.t('removeIt')
div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"')
p
|&nbsp;
=env.t('brokenChallenge')
p
a(ng-click='unlink(task, "keep-all")')=env.t('keepThem')
| &nbsp;|&nbsp;
a(ng-click='unlink(task, "remove-all")')=env.t('removeThem')
div(ng-if='task.challenge.broken=="CHALLENGE_CLOSED"')
p
!=env.t('challengeCompleted', {user: "{{task.challenge.winner}}"})
p
a(ng-click='unlink(task, "keep-all")')=env.t('keepThem')
| &nbsp;|&nbsp;
a(ng-click='unlink(task, "remove-all")')=env.t('removeThem')
//div(ng-if='task.challenge.broken=="UNSUBSCRIBED"')
p=env.t('unsubChallenge')
p
a(ng-click="unlink(task, 'keep-all')")=env.t('keepThem')
| &nbsp;|&nbsp;
a(ng-click="unlink(task, 'remove-all')")=env.t('removeThem')
include ./checklist
form(ng-submit='saveTask(task,false,true)')
include ./text_notes
include ./habits/plus_minus
include ./dailies/calendar
include ./rewards/pricing
include ./todos/due_date
include ./tags
include ./advanced_options
.save-close
button(type='submit')=env.t('saveAndClose')

View File

@@ -0,0 +1,5 @@
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
legend.option-title=env.t('price')
input.option-content(type='number', size='16', min='0', step='any', ng-model='task.value', required)
.money.input-suffix
span.shop_gold

View File

@@ -0,0 +1,5 @@
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
p.option-title.mega(ng-click='task._tags = !task._tags', tooltip=env.t('expandCollapse'))=env.t('tags')
label.checkbox(ng-repeat='tag in user.tags', ng-class="{visuallyhidden: task._tags}")
input(type='checkbox', ng-model='task.tags[tag.id]')
markdown(text='tag.name')

View File

@@ -0,0 +1,6 @@
fieldset.option-group
label.option-title=env.t('text')
input.option-content(type='text', ng-model='task.text', required, ng-disabled='task.challenge.id')
label.option-title=env.t('extraNotes')
textarea.option-content(rows='3', ng-model='task.notes', ng-model-options="{debounce: 1000}")

View File

@@ -0,0 +1,6 @@
fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
legend.option-title=env.t('dueDate')
input.option-content.datepicker(type='text', ng-model='task.date',
datepicker-popup='{{::user.preferences.dateFormat}}', is-open='datepickerOpened',
ng-click='datepickerOpened = true')

View File

@@ -0,0 +1,39 @@
// Note here, we need this part of Habit to be a directive since we're going to be passing it variables from various
// parts of the app. The alternative would be to create new scopes for different containing sections, but that
// started to get unwieldy
include ./task_view/mixins
script(id='templates/habitrpg-tasks.html', type="text/ng-template")
.tasks-lists.container-fluid
.row
.col-md-3.col-sm-6(ng-repeat='list in lists', ng-class='::{"rewards-module": list.type==="reward"}')
.task-column(class='{{::list.type}}s')
include ./task_view/graph
h2.task-column_title {{::list.header}}
include ./task_view/help
.todos-chart(ng-if='::list.type == "todo"', ng-show='charts.todos')
include ./task_view/add_new
alert.alert-warning.dailiesRestingInInn(ng-if='::list.type == "daily" && user.preferences.sleep')
i.glyphicon.glyphicon-warning-sign &nbsp;
=env.t('dailiesRestingInInn')
+taskColumnTabs('top')
// Actual List
ul(class='{{::list.type}}s main-list', ng-show='obj[list.type + "s"].length > 0', hrpg-sort-tasks, ng-if='!$state.includes("options.social.challenges")')
include ./task
//Loads the non-sortable lists for challenges
ul(class='{{::list.type}}s main-list', ng-show='obj[list.type + "s"].length > 0', ng-if='$state.includes("options.social.challenges")')
include ./task
include ./task_view/static_rewards
include ./task_view/spells
+taskColumnTabs('bottom')

View File

@@ -1,188 +0,0 @@
// Note here, we need this part of Habit to be a directive since we're going to be passing it variables from various
// parts of the app. The alternative would be to create new scopes for different containing sections, but that
// started to get unwieldy
script(id='templates/habitrpg-tasks.html', type="text/ng-template")
.tasks-lists.container-fluid
.row
.col-md-3.col-sm-6(bindonce='lists', ng-repeat='list in lists', ng-class='::{"rewards-module": list.type==="reward"}')
.task-column(class='{{list.type}}s')
// Todos export/graph options
span.option-box.pull-right(ng-if='::main')
a.option-action(ng-if='list.type=="todo"', ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'), style='margin-right:5px;')
span.glyphicon.glyphicon-signal
//a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
//-a.option-action(ng-if='list.type=="todo"', ng-click='notPorted()', tooltip='iCal', ng-show='false')
span.glyphicon.glyphicon-calendar
// <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.option-action(ng-click='list.help=!list.help', tooltip='Click for help')
span.glyphicon.glyphicon-question-sign(style={'zoom':1.5,'vertical-align':'-webkit-baseline-middle'})
// Header
h2.task-column_title
| {{list.header}}
div(ng-if='list.help', ng-switch='::list.type')
div(ng-switch-when='habit')
ul
li!=env.t('habitHelp1', {plusIcon:"<span class='glyphicon glyphicon-plus'></span>"})
li!=env.t('habitHelp2', {minusIcon:"<span class='glyphicon glyphicon-minus'></span>"})
li!=env.t('habitHelp3')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})
div(ng-switch-when='daily')
ul
li!=env.t('dailyHelp1', {emphasisStart:"<strong>", emphasisEnd:"</strong>", pencilIcon:"<span class='glyphicon glyphicon-pencil'></span>"})
li!=env.t('dailyHelp2')
li!=env.t('dailyHelp3', {emphasisStart:"<strong>", emphasisEnd:"</strong>"})
li!=env.t('dailyHelp4', {linkStart:"<a href='/#/options/settings/settings' target='_blank'>", linkEnd:"</a>"})
li!=env.t('dailyHelp5')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})
div(ng-switch-when='todo')
ul
li!=env.t('toDoHelp1')
li!=env.t('toDoHelp2')
li!=env.t('toDoHelp3')
li!=env.t('toDoHelp4')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})
div(ng-switch-when='reward')
ul
li!=env.t('rewardHelp1', {linkStart:"<a href='/#/options/inventory/equipment' target='_blank'>", linkEnd: "</a>"})
li!=env.t('rewardHelp2', {linkStart:"<a href='/#/options/profile/stats' target='_blank'>", linkEnd: "</a>"})
li!=env.t('rewardHelp3')
li!=env.t('rewardHelp4')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})
// Todo Chart
.todos-chart(ng-if='::list.type == "todo"', ng-show='charts.todos')
// Add New
form.task-add(name='new{{list.type}}form', ng-hide='obj._locked', ng-submit='addTask(obj[list.type+"s"],list)' novalidate)
textarea(rows='6', task-focus='list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolderBulk}}', ng-if='list.bulk', ui-keydown='{"meta-enter ctrl-enter":"addTask(obj[list.type+\'s\'],list)"}', required)
input(type='text', task-focus='!list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolder}}', ng-if='!list.bulk', required)
button(type='submit', ng-disabled='new{{list.type}}form.$invalid')
div.empty-task-notification( ng-show='new{{list.type}}form.$invalid', tooltip=env.t("emptyTask") )
span.glyphicon.glyphicon-plus
span.glyphicon.glyphicon-plus(ng-show='!new{{list.type}}form.$invalid')
small.help-block.btn-link.pull-right(ng-click='toggleBulk(list)')
span(ng-if='!list.bulk')=env.t('addmultiple')
span(ng-if='list.bulk')=env.t('addsingle')
alert.alert-warning.dailiesRestingInInn(ng-if='::list.type == "daily" && user.preferences.sleep')
i.glyphicon.glyphicon-warning-sign &nbsp;
=env.t('dailiesRestingInInn')
mixin taskColumnTabs(position)
// Habits Tabs
div(ng-if='::main && list.type=="habit"', class='tabbable tabs-below')
ul.task-filter
li(ng-class='{active: list.view == "all"}')
a(ng-click='list.view = "all"')=env.t('all')
li(ng-class='{active: list.view == "yellowred"}')
a(ng-click='list.view = "yellowred"')=env.t('yellowred')
li(ng-class='{active: list.view == "greenblue"}')
a(ng-click='list.view = "greenblue"')=env.t('greenblue')
// Daily Tabs
div(ng-if='::main && list.type=="daily"', class='tabbable tabs-below')
// remaining/completed tabs
ul.task-filter
li(ng-class='{active: list.view == "all"}')
a(ng-click='list.view = "all"')=env.t('all')
li(ng-class='{active: list.view == "remaining"}')
a(ng-click='list.view = "remaining"')=env.t('due')
li(ng-class='{active: list.view == "complete"}')
a(ng-click='list.view = "complete"')=env.t('grey')
// Todo Tabs
div(ng-if='::main && list.type=="todo"', ng-class='::{"tabbable tabs-below": list.type=="todo"}')
if position=="bottom"
div(ng-show='list.view == "complete"')
.alert
=env.t('lotOfToDos')
button.task-action-btn.tile.spacious.bright(ng-click='user.ops.clearCompleted({})',popover=env.t('deleteToDosExplanation'),popover-trigger='mouseenter')=env.t('clearCompleted')
p!=env.t('beeminderDeleteWarning')
// remaining/completed tabs
ul.task-filter
li(ng-class='{active: list.view == "remaining"}')
a(ng-click='list.view = "remaining"')=env.t('remaining')
li(ng-class='{active: list.view == "dated"}')
a(ng-click='list.view = "dated"')=env.t('dated')
li(ng-class='{active: list.view == "complete"}')
a(ng-click='list.view = "complete"')=env.t('complete')
// Rewards Tabs
div(ng-if='::main && list.type=="reward"', class='tabbable tabs-below')
ul.task-filter
li(ng-class='{active: list.view == "all"}')
a(ng-click='list.view = "all"')=env.t('all')
li(ng-class='{active: list.view == "ingamerewards"}')
a(ng-click='list.view = "ingamerewards"')=env.t('ingamerewards')
+taskColumnTabs('top')
// Actual List
ul(class='{{list.type}}s main-list', ng-show='obj[list.type + "s"].length > 0', hrpg-sort-tasks, ng-if='!$state.includes("options.social.challenges")')
include ./task
//Loads the non-sortable lists for challenges
ul(class='{{list.type}}s main-list', ng-show='obj[list.type + "s"].length > 0', ng-if='$state.includes("options.social.challenges")')
include ./task
// Static Rewards
ul.items.rewards(ng-if='main && list.type=="reward"')
li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.key == "armoire" && !user.flags.armoireEmpty ? env.t("armoireNotesFull") + armoireCount(user.items.gear.owned) : item.notes()}}')
// right-hand side control buttons
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
a.money.btn-buy.item-btn(ng-class='{highValue: item.value >= 1000}', ng-click='buy(item)')
span.shop_gold
span.reward-cost {{item.value}}
// main content
span(ng-class='::{"shop_{{item.key}} shop-sprite item-img": true}').reward-img
p.task-text {{item.text()}}
// Events
ul.items.rewards(ng-if='main && list.type=="reward" && (user.items.special.snowball>0 || user.stats.buffs.snowball || user.items.special.spookDust>0 || user.stats.buffs.spookDust || user.items.special.shinySeed>0 || user.stats.buffs.shinySeed)')
mixin specialSpell(k,canceler)
li.task.reward-item(ng-if='#{canceler ? "user.stats.buffs."+canceler : "user.items.special."+k+">0"}',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.#{k}.notes()}}')
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
a.money.btn-buy.item-btn(ng-click='castStart(Content.spells.special.#{k})', ng-class='{active: Content.spells.special.#{k}.key == spell.key}')
if canceler
span.shop_gold
span.reward-cost {{Content.spells.special.#{k}.value}}
else
span.shop_spell(class='shop_#{k}')
span.reward-cost {{user.items.special.#{k}}}
// main content
p.task-text {{Content.spells.special.#{k}.text()}}
+specialSpell('snowball')
+specialSpell('spookDust')
+specialSpell('shinySeed')
+specialSpell('salt','snowball')
+specialSpell('opaquePotion','spookDust')
+specialSpell('petalFreePotion','shinySeed')
// Spells
ul.items(ng-if='main && list.type=="reward" && user.stats.class && !user.preferences.disableClasses')
li.task.reward-item(ng-repeat='(k,skill) in Content.spells[user.stats.class]', ng-if='user.stats.lvl >= skill.lvl',popover-trigger='mouseenter', popover-placement='top', popover='{{skill.notes()}}')
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
a.money.btn-buy.item-btn(ng-click='castStart(skill)', ng-class='{active: skill.key == spell.key}')
span.reward-cost
strong {{skill.mana}}
=env.t('mp')
// main content
span(ng-class='{"shop_{{skill.key}} shop-sprite item-img": true}')
p.task-text {{skill.text()}}
br
+taskColumnTabs('bottom')

View File

@@ -0,0 +1,56 @@
.task-meta-controls
// Due Date
span(ng-if='task.type=="todo" && task.date')
span(ng-class='{"label label-danger":(moment(task.date).isBefore(_today, "days") && !task.completed)}') {{task.date | date:(user.preferences.dateFormat.indexOf('yyyy') == 0 ? user.preferences.dateFormat.substr(5) : user.preferences.dateFormat.substr(0,5))}}
// Streak
| &nbsp;
span(ng-show='task.streak') {{task.streak}}&nbsp;
span(tooltip=env.t('streakCounter'))
span.glyphicon.glyphicon-forward
| &nbsp;
// Icons only available if you own the tasks (aka, hidden from challenge stats)
span(ng-if='!obj._locked')
a(ng-click='pushTask(task,$index,"top")', tooltip=env.t('pushTaskToTop'))
span.glyphicon.glyphicon-open
// a(ng-click='pushTask(task,$index,"bottom")', tooltip=env.t('pushTaskToBottom'))
// span.glyphicon.glyphicon-import
// // glyphicon-import or glyphicon-save or glyphicon-sort-by-attributes
a.badge(ng-if='task.checklist[0]', ng-class='{"badge-success":checklistCompletion(task.checklist) == task.checklist.length}', ng-click='collapseChecklist(task)', tooltip=env.t('expandCollapse'))
|{{checklistCompletion(task.checklist)}}/{{task.checklist.length}}
span.glyphicon.glyphicon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
// edit
a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit'))
| &nbsp;
span.glyphicon.glyphicon-pencil(ng-hide='task._editing')
| &nbsp;
a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip=env.t('cancel'))
span.glyphicon.glyphicon-remove(ng-hide='!task._editing')
| &nbsp;
// save
a(ng-hide='!task._editing', ng-click='editTask(task);saveTask(task)', tooltip=env.t('save'))
span.glyphicon.glyphicon-ok(ng-hide='!task._editing')
| &nbsp;
//challenges
span(ng-if='task.challenge.id')
span(ng-if='task.challenge.broken')
span.glyphicon.glyphicon-bullhorn(style='background-color:red;', ng-click='task._editing = true', tooltip=env.t('brokenChaLink') tooltip-placement='right')
| &nbsp;
span(ng-if='!task.challenge.broken')
span.glyphicon.glyphicon-bullhorn(tooltip=env.t('challenge'))
| &nbsp;
// delete
a(ng-if='!task.challenge.id', ng-click='removeTask(task, obj[list.type+"s"])', tooltip=env.t('delete'))
span.glyphicon.glyphicon-trash
| &nbsp;
// chart
a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip=env.t('progress'))
span.glyphicon.glyphicon-signal
| &nbsp;
// notes
span.task-notes(ng-show='task.notes && !task._editing', ng-click='task.popoverOpen = !task.popoverOpen', popover-trigger='click', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}')
span.glyphicon.glyphicon-comment
| &nbsp;

View File

@@ -1,238 +1,17 @@
li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | filterByTextAndNotes: obj.filterQuery | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.popoverOpen ? '' : task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
// right-hand side control buttons
.task-meta-controls
li(id='task-{{::task.id}}',
ng-repeat='task in obj[list.type+"s"] | filterByTextAndNotes: obj.filterQuery | conditionalOrderBy: list.view=="dated":"date"',
class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}',
ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}',
ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)',
ng-show='shouldShow(task, list, user.preferences)',
popover-trigger='mouseenter', popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}',
data-popover-html="{{task.popoverOpen ? '' : task.notes | markdown}}")
// Due Date
span(ng-if='task.type=="todo" && task.date')
span(ng-class='{"label label-danger":(moment(task.date).isBefore(_today, "days") && !task.completed)}') {{task.date | date:(user.preferences.dateFormat.indexOf('yyyy') == 0 ? user.preferences.dateFormat.substr(5) : user.preferences.dateFormat.substr(0,5))}}
ng-form(name='taskForm')
include ./meta_controls
// Streak
| &nbsp;
span(ng-show='task.streak') {{task.streak}}&nbsp;
span(tooltip=env.t('streakCounter'))
span.glyphicon.glyphicon-forward
| &nbsp;
include ./task_view/index
// Icons only available if you own the tasks (aka, hidden from challenge stats)
span(ng-if='!obj._locked')
a(ng-click='pushTask(task,$index,"top")', tooltip=env.t('pushTaskToTop'))
span.glyphicon.glyphicon-open
// a(ng-click='pushTask(task,$index,"bottom")', tooltip=env.t('pushTaskToBottom'))
// span.glyphicon.glyphicon-import
// // glyphicon-import or glyphicon-save or glyphicon-sort-by-attributes
a.badge(ng-if='task.checklist[0]', ng-class='{"badge-success":checklistCompletion(task.checklist) == task.checklist.length}', ng-click='collapseChecklist(task)', tooltip=env.t('expandCollapse'))
|{{checklistCompletion(task.checklist)}}/{{task.checklist.length}}
span.glyphicon.glyphicon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
// edit
a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit'))
| &nbsp;
span.glyphicon.glyphicon-pencil(ng-hide='task._editing')
| &nbsp;
a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip=env.t('cancel'))
span.glyphicon.glyphicon-remove(ng-hide='!task._editing')
| &nbsp;
// save
a(ng-hide='!task._editing', ng-click='editTask(task);saveTask(task)', tooltip=env.t('save'))
span.glyphicon.glyphicon-ok(ng-hide='!task._editing')
| &nbsp;
//challenges
span(ng-if='task.challenge.id')
span(ng-if='task.challenge.broken')
span.glyphicon.glyphicon-bullhorn(style='background-color:red;', ng-click='task._editing = true', tooltip=env.t('brokenChaLink') tooltip-placement='right')
| &nbsp;
span(ng-if='!task.challenge.broken')
span.glyphicon.glyphicon-bullhorn(tooltip=env.t('challenge'))
| &nbsp;
// delete
a(ng-if='!task.challenge.id', ng-click='removeTask(task, obj[list.type+"s"])', tooltip=env.t('delete'))
span.glyphicon.glyphicon-trash
| &nbsp;
// chart
a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip=env.t('progress'))
span.glyphicon.glyphicon-signal
| &nbsp;
// notes
span.task-notes(ng-show='task.notes && !task._editing', ng-click='task.popoverOpen = !task.popoverOpen', popover-trigger='click', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}')
span.glyphicon.glyphicon-comment
| &nbsp;
// left-hand side checkbox
.task-controls.task-primary(ng-if='!task._editing')
// Habits
.task-actions(ng-if='::task.type=="habit"')
// score() is overridden in challengesCtrl to do nothing
a(ng-if='task.up', ng-click='applyingAction || score(task,"up")')
span.glyphicon.glyphicon-plus
a(ng-if='task.down', ng-click='applyingAction || score(task,"down")')
span.glyphicon.glyphicon-minus
// Rewards
span(ng-show='task.type=="reward"')
a.money.btn-buy(ng-class='{highValue: task.value >= 1000}', ng-click='score(task, "down")')
span.shop_gold
span.reward-cost {{task.value}}
// Daily & Todos
span.task-checker.action-yesno(ng-if='::task.type=="daily" || task.type=="todo"')
input.visuallyhidden.focusable(ng-if='$state.includes("tasks")', id='box-{{obj._id}}_{{task.id}}', type='checkbox', ng-model='task.completed', ng-change='task.type=="todo" && pushTask(task,$index,"bottom"); changeCheck(task)')
input.visuallyhidden.focusable(ng-if='!$state.includes("tasks")', id='box-{{obj._id}}_{{task.id}}', type='checkbox')
label(for='box-{{obj._id}}_{{task.id}}')
// main content
div.task-text(ng-dblclick='task._editing ? saveTask(task) : editTask(task)')
markdown(text='task.text',target='_blank')
//-| {{task.text}}
div(ng-if='task.checklist && !$state.includes("options.social.challenges") && !task.collapseChecklist && !task._editing')
fieldset.option-group.task-checklist
label.checkbox(ng-repeat='item in task.checklist')
input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
markdown(text='item.text',target='_blank')
// edit/options dialog
div(ng-if='task._editing')
.task-options
// Broken Challenge
.well(ng-if='task.challenge.broken')
div(ng-if='task.challenge.broken=="TASK_DELETED"')
p=env.t('brokenTask')
p
a(ng-click='unlink(task, "keep")')=env.t('keepIt')
| &nbsp;&nbsp;
a(ng-click="removeTask(task, obj[list.type+'s'])")=env.t('removeIt')
div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"')
p
|&nbsp;
=env.t('brokenChallenge')
p
a(ng-click='unlink(task, "keep-all")')=env.t('keepThem')
| &nbsp;|&nbsp;
a(ng-click='unlink(task, "remove-all")')=env.t('removeThem')
div(ng-if='task.challenge.broken=="CHALLENGE_CLOSED"')
p
!=env.t('challengeCompleted', {user: "{{task.challenge.winner}}"})
p
a(ng-click='unlink(task, "keep-all")')=env.t('keepThem')
| &nbsp;|&nbsp;
a(ng-click='unlink(task, "remove-all")')=env.t('removeThem')
//div(ng-if='task.challenge.broken=="UNSUBSCRIBED"')
p=env.t('unsubChallenge')
p
a(ng-click="unlink(task, 'keep-all')")=env.t('keepThem')
| &nbsp;|&nbsp;
a(ng-click="unlink(task, 'remove-all')")=env.t('removeThem')
// Checklists
.task-checklist-edit(ng-if='!$state.includes("options.social.challenges")')
ul
li
button(type='button', ng-if='!task.checklist[0] && (task.type=="daily" || task.type=="todo")',ng-click='addChecklist(task)')
span.glyphicon.glyphicon-tasks
span=env.t('addChecklist')
form.checklist-form(ng-if='task.checklist')
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
legend.option-title
span.hint(popover=env.t('checklistText'),popover-trigger='mouseenter',popover-placement='bottom')=env.t('checklist')
ul(hrpg-sort-checklist)
li(ng-repeat='item in task.checklist')
//input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
//-,ng-blur='saveTask(task,true)')
span.checklist-icon.glyphicon.glyphicon-resize-vertical()
input(type='text',ng-model='item.text', ui-keyup="{'13':'addChecklistItem(task,$event,$index)','38 40':'navigateChecklist(task,$index,$event)'}")
a(ng-click='removeChecklistItem(task,$event,$index,true)')
span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))
form(ng-submit='saveTask(task,false,true)')
// text & notes
fieldset.option-group
label.option-title=env.t('text')
input.option-content(type='text', ng-model='task.text', required, ng-disabled='task.challenge.id')
label.option-title=env.t('extraNotes')
textarea.option-content(rows='3', ng-model='task.notes', ng-model-options="{debounce: 1000}")
// if Habit, plus/minus command options
fieldset.option-group.plusminus(ng-if='task.type=="habit" && !task.challenge.id')
legend.option-title=env.t('direction/Actions')
span.task-checker
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
label(for='{{obj._id}}_{{task.id}}-option-plus')
span.task-checker
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
label(for='{{obj._id}}_{{task.id}}-option-minus')
// if Daily, calendar
fieldset(ng-if='::task.type=="daily"', class="option-group")
legend.option-title=env.t('repeat')
ul.repeat-days(bindonce)
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
li
button(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)') {{::moment.weekdaysMin(0)}}
li
button(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)') {{::moment.weekdaysMin(1)}}
li
button(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)') {{::moment.weekdaysMin(2)}}
li
button(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)') {{::moment.weekdaysMin(3)}}
li
button(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)') {{::moment.weekdaysMin(4)}}
li
button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
li
button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
// if Reward, pricing
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
legend.option-title=env.t('price')
input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value')
.money.input-suffix
span.shop_gold
// if Todos, the due date
fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
legend.option-title=env.t('dueDate')
input.option-content.datepicker(type='text', datepicker-popup='{{user.preferences.dateFormat}}', ng-model='task.date', is-open='datepickerOpened', ng-click='datepickerOpened = true')
// Tags
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
p.option-title.mega(ng-click='task._tags = !task._tags', tooltip=env.t('expandCollapse'))=env.t('tags')
label.checkbox(ng-repeat='tag in user.tags', ng-class="{visuallyhidden: task._tags}")
input(type='checkbox', ng-model='task.tags[tag.id]')
markdown(text='tag.name')
// Advanced Options
span(ng-if='::task.type!="reward"')
p.option-title.mega(ng-click='task._advanced = !task._advanced', tooltip=env.t('expandCollapse'))=env.t('advancedOptions')
fieldset.option-group.advanced-option(ng-class="{visuallyhidden: task._advanced}")
legend.option-title
a.hint.priority-multiplier-help(href='http://habitrpg.wikia.com/wiki/Difficulty', target='_blank', popover-title=env.t('difficultyHelpTitle'), popover-trigger='mouseenter', popover=env.t('difficultyHelpContent'))=env.t('difficulty')
ul.priority-multiplier
li
button(type='button', ng-class='{active: task.priority==1 || !task.priority}', ng-click='task.challenge.id || (task.priority=1)')=env.t('easy')
li
button(type='button', ng-class='{active: task.priority==1.5}', ng-click='task.challenge.id || (task.priority=1.5)')=env.t('medium')
li
button(type='button', ng-class='{active: task.priority==2}', ng-click='task.challenge.id || (task.priority=2)')=env.t('hard')
//span(ng-if='task.type=="daily" && !task.challenge.id')
span(ng-if='task.type=="daily"')
legend.option-title.pull-left=env.t('restoreStreak')
input.option-content(type='number', ng-model='task.streak')
div(ng-if='(user.preferences.allocationMode == "taskbased" && user.preferences.automaticAllocation) || $state.is("options.social.challenges")')
legend.option-title.pull-left=env.t('attributes')
ul.task-attributes
li
button(type='button', ng-class='{active: task.attribute=="str"}', ng-click='task.attribute="str"')=env.t('physical')
li
button(type='button', ng-class='{active: task.attribute=="int"}', ng-click='task.attribute="int"')=env.t('mental')
li
button(type='button', ng-class='{active: task.attribute=="con"}', ng-click='task.attribute="con"')=env.t('social')
li
button(type='button', ng-class='{active: task.attribute=="per"}', ng-click='task.attribute="per"', popover=env.t('otherExamples'), popover-trigger='mouseenter', popover-placement='top')=env.t('other')
.save-close
button(type='submit')=env.t('saveAndClose')
include ./edit/index
div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]')

View File

@@ -0,0 +1,10 @@
form.task-add(name='new{{list.type}}form', ng-hide='obj._locked', ng-submit='addTask(obj[list.type+"s"],list)', novalidate)
textarea(rows='6', task-focus='list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolderBulk}}', ng-if='list.bulk', ui-keydown='{"meta-enter ctrl-enter":"addTask(obj[list.type+\'s\'],list)"}', required)
input(type='text', task-focus='!list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolder}}', ng-if='!list.bulk', required)
button(type='submit', ng-disabled='new{{list.type}}form.$invalid')
div.empty-task-notification( ng-show='new{{list.type}}form.$invalid', tooltip=env.t("emptyTask") )
span.glyphicon.glyphicon-plus
span.glyphicon.glyphicon-plus(ng-show='!new{{list.type}}form.$invalid')
small.help-block.btn-link.pull-right(ng-click='toggleBulk(list)')
span(ng-if='!list.bulk')=env.t('addmultiple')
span(ng-if='list.bulk')=env.t('addsingle')

View File

@@ -0,0 +1,9 @@
span.option-box.pull-right(ng-if='::main')
a.option-action(ng-if='list.type=="todo"', ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'), style='margin-right:5px;')
span.glyphicon.glyphicon-signal
//a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
//-a.option-action(ng-if='list.type=="todo"', ng-click='notPorted()', tooltip='iCal', ng-show='false')
span.glyphicon.glyphicon-calendar
// <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.option-action(ng-click='list.help=!list.help', tooltip='Click for help')
span.glyphicon.glyphicon-question-sign(style={'zoom':1.5,'vertical-align':'-webkit-baseline-middle'})

View File

@@ -0,0 +1,25 @@
div(ng-if='list.help', ng-switch='::list.type')
ul(ng-switch-when='habit')
li!=env.t('habitHelp1', {plusIcon:"<span class='glyphicon glyphicon-plus'></span>"})
li!=env.t('habitHelp2', {minusIcon:"<span class='glyphicon glyphicon-minus'></span>"})
li!=env.t('habitHelp3')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})
ul(ng-switch-when='daily')
li!=env.t('dailyHelp1', {emphasisStart:"<strong>", emphasisEnd:"</strong>", pencilIcon:"<span class='glyphicon glyphicon-pencil'></span>"})
li=env.t('dailyHelp2')
li!=env.t('dailyHelp3', {emphasisStart:"<strong>", emphasisEnd:"</strong>"})
li!=env.t('dailyHelp4', {linkStart:"<a href='/#/options/settings/settings' target='_blank'>", linkEnd:"</a>"})
li!=env.t('dailyHelp5')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})
ul(ng-switch-when='todo')
li=env.t('toDoHelp1')
li=env.t('toDoHelp2')
li=env.t('toDoHelp3')
li!=env.t('toDoHelp4')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})
ul(ng-switch-when='reward')
li!=env.t('rewardHelp1', {linkStart:"<a href='/#/options/inventory/equipment' target='_blank'>", linkEnd: "</a>"})
li!=env.t('rewardHelp2', {linkStart:"<a href='/#/options/profile/stats' target='_blank'>", linkEnd: "</a>"})
li=env.t('rewardHelp3')
li!=env.t('rewardHelp4')
li!=env.t('newbieGuild', {linkStart:"<a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>", linkEnd: "</a>"})

View File

@@ -0,0 +1,35 @@
// left-hand side checkbox
.task-controls.task-primary(ng-if='!task._editing')
// Habits
.task-actions(ng-if='::task.type=="habit"')
// score() is overridden in challengesCtrl to do nothing
a(ng-if='task.up', ng-click='applyingAction || score(task,"up")')
span.glyphicon.glyphicon-plus
a(ng-if='task.down', ng-click='applyingAction || score(task,"down")')
span.glyphicon.glyphicon-minus
// Rewards
span(ng-if='::task.type=="reward"')
a.money.btn-buy(ng-class='{highValue: task.value >= 1000}', ng-click='score(task, "down")')
span.shop_gold
span.reward-cost {{task.value}}
// Daily & Todos
span.task-checker.action-yesno(ng-if='::task.type=="daily" || task.type=="todo"')
input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task.id}}', type='checkbox',
ng-model='task.completed', ng-if='$state.includes("tasks")',
ng-change='task.type=="todo" && pushTask(task,$index,"bottom"); changeCheck(task)')
input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task.id}}', type='checkbox',
ng-if='!$state.includes("tasks")')
label(for='box-{{::obj._id}}_{{::task.id}}')
// main content
.task-text(ng-dblclick='task._editing ? saveTask(task) : editTask(task)')
markdown(text='task.text',target='_blank')
div(ng-if='task.checklist && !$state.includes("options.social.challenges") && !task.collapseChecklist && !task._editing')
fieldset.option-group.task-checklist
label.checkbox(ng-repeat='item in task.checklist')
input(type='checkbox', ng-model='item.completed', ng-change='saveTask(task,true)')
markdown(text='item.text', target='_blank')

View File

@@ -0,0 +1,60 @@
mixin taskColumnTabs(position)
// Habits Tabs
div(ng-if='::main && list.type=="habit"', class='tabbable tabs-below')
ul.task-filter
li(ng-class='{active: list.view == "all"}')
a(ng-click='list.view = "all"')=env.t('all')
li(ng-class='{active: list.view == "yellowred"}')
a(ng-click='list.view = "yellowred"')=env.t('yellowred')
li(ng-class='{active: list.view == "greenblue"}')
a(ng-click='list.view = "greenblue"')=env.t('greenblue')
// Daily Tabs
div(ng-if='::main && list.type=="daily"', class='tabbable tabs-below')
// remaining/completed tabs
ul.task-filter
li(ng-class='{active: list.view == "all"}')
a(ng-click='list.view = "all"')=env.t('all')
li(ng-class='{active: list.view == "remaining"}')
a(ng-click='list.view = "remaining"')=env.t('due')
li(ng-class='{active: list.view == "complete"}')
a(ng-click='list.view = "complete"')=env.t('grey')
// Todo Tabs
div(ng-if='::main && list.type=="todo"', ng-class='::{"tabbable tabs-below": list.type=="todo"}')
if position=="bottom"
div(ng-show='list.view == "complete"')
.alert
=env.t('lotOfToDos')
button.task-action-btn.tile.spacious.bright(ng-click='user.ops.clearCompleted({})',popover=env.t('deleteToDosExplanation'),popover-trigger='mouseenter')=env.t('clearCompleted')
p!=env.t('beeminderDeleteWarning')
// remaining/completed tabs
ul.task-filter
li(ng-class='{active: list.view == "remaining"}')
a(ng-click='list.view = "remaining"')=env.t('remaining')
li(ng-class='{active: list.view == "dated"}')
a(ng-click='list.view = "dated"')=env.t('dated')
li(ng-class='{active: list.view == "complete"}')
a(ng-click='list.view = "complete"')=env.t('complete')
// Rewards Tabs
div(ng-if='::main && list.type=="reward"', class='tabbable tabs-below')
ul.task-filter
li(ng-class='{active: list.view == "all"}')
a(ng-click='list.view = "all"')=env.t('all')
li(ng-class='{active: list.view == "ingamerewards"}')
a(ng-click='list.view = "ingamerewards"')=env.t('ingamerewards')
mixin specialSpell(k,canceler)
li.task.reward-item(ng-if='#{canceler ? "user.stats.buffs."+canceler : "user.items.special."+k+">0"}',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.#{k}.notes()}}')
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
a.money.btn-buy.item-btn(ng-click='castStart(Content.spells.special.#{k})', ng-class='{active: Content.spells.special.#{k}.key == spell.key}')
if canceler
span.shop_gold
span.reward-cost {{Content.spells.special.#{k}.value}}
else
span.shop_spell(class='shop_#{k}')
span.reward-cost {{user.items.special.#{k}}}
// main content
p.task-text {{Content.spells.special.#{k}.text()}}

View File

@@ -0,0 +1,25 @@
// Events
ul.items.rewards(ng-if='main && list.type=="reward" && (user.items.special.snowball>0 || user.stats.buffs.snowball || user.items.special.spookDust>0 || user.stats.buffs.spookDust || user.items.special.shinySeed>0 || user.stats.buffs.shinySeed)')
+specialSpell('snowball')
+specialSpell('spookDust')
+specialSpell('shinySeed')
+specialSpell('salt','snowball')
+specialSpell('opaquePotion','spookDust')
+specialSpell('petalFreePotion','shinySeed')
// Spells
ul.items(ng-if='main && list.type=="reward" && user.stats.class && !user.preferences.disableClasses')
li.task.reward-item(ng-repeat='(k,skill) in Content.spells[user.stats.class]', ng-if='user.stats.lvl >= skill.lvl',popover-trigger='mouseenter', popover-placement='top', popover='{{skill.notes()}}')
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
a.money.btn-buy.item-btn(ng-click='castStart(skill)', ng-class='{active: skill.key == spell.key}')
span.reward-cost
strong {{skill.mana}}
=env.t('mp')
// main content
span(ng-class='{"shop_{{skill.key}} shop-sprite item-img": true}')
p.task-text {{skill.text()}}

View File

@@ -0,0 +1,14 @@
ul.items.rewards(ng-if='main && list.type=="reward"')
li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.key == "armoire" && !user.flags.armoireEmpty ? env.t("armoireNotesFull") + armoireCount(user.items.gear.owned) : item.notes()}}')
// right-hand side control buttons
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
a.money.btn-buy.item-btn(ng-class='{highValue: item.value >= 1000}', ng-click='buy(item)')
span.shop_gold
span.reward-cost {{item.value}}
// main content
span(ng-class='::{"shop_{{item.key}} shop-sprite item-img": true}').reward-img
p.task-text {{item.text()}}