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.", "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", "classEquipBonus": "Class Bonus",
"battleGear": "Battle Gear", "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", "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.", "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", "useCostume": "Use Costume",

View File

@@ -70,7 +70,6 @@
"lastLoggedIn": "- Last logged in", "lastLoggedIn": "- Last logged in",
"notPorted": "This feature is not yet ported from the original site.", "notPorted": "This feature is not yet ported from the original site.",
"buyThis": "Buy this <%= text %> with <%= price %> of your <%= gems %> Gems?", "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", "noReachServer": "Server not currently reachable, try again later",
"errorUpCase": "ERROR:", "errorUpCase": "ERROR:",
"newPassSent": "New password sent.", "newPassSent": "New password sent.",

View File

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

View File

@@ -3,7 +3,7 @@
"quest": "quest", "quest": "quest",
"completed": "Completed!", "completed": "Completed!",
"youReceived": "You've Received", "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", "inviteParty": "Invite Party",
"questInvitation": "Quest Invitation: ", "questInvitation": "Quest Invitation: ",
"questInvitationTitle": "Quest Invitation", "questInvitationTitle": "Quest Invitation",

View File

@@ -36,6 +36,11 @@
"newDailyBulk": "New Dailies (one per line)", "newDailyBulk": "New Dailies (one per line)",
"streakCounter": "Streak Counter", "streakCounter": "Streak Counter",
"repeat": "Repeat", "repeat": "Repeat",
"repeatEvery": "Repeat Every",
"repeatDays": "Every X Days",
"repeatWeek": "On Certain Days of the Week",
"day": "Day",
"days": "Days",
"restoreStreak": "Restore Streak", "restoreStreak": "Restore Streak",
"todos": "To-Dos", "todos": "To-Dos",
"newTodo": "New To-Do", "newTodo": "New To-Do",
@@ -60,6 +65,9 @@
"clearTags": "Clear", "clearTags": "Clear",
"hideTags": "Hide", "hideTags": "Hide",
"showTags": "Show", "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", "streakName": "Streak Achievements",
"streakText": "Has performed <%= streaks %> 21-day streaks on Dailies", "streakText": "Has performed <%= streaks %> 21-day streaks on Dailies",
"streakSingular": "Streaker", "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.", "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 %>.", "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 %>.", "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 %>!", "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.", "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.", "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.", "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.", "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.", "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.", "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!", "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 %>.", "rewardHelp1": "The Equipment you buy for your avatar is stored in <%= linkStart %>Inventory > Equipment<%= linkEnd %>.",
"rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).", "rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here during World Events.", "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} repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true}
api.userDefaults = api.userDefaults =
habits: [ habits: [
{type: 'habit', text: t('defaultHabit1Text'), notes: t('defaultTaskNotes'), value: 0, up: true, down: false, attribute: 'per' } {type: 'habit', text: t('defaultHabit1Text'), 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('defaultHabit2Text'), 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('defaultHabit3Text'), value: 0, up: true, down: true, attribute: 'str'}
] ]
dailys: [ dailys: [
@@ -1898,7 +1898,7 @@ api.userDefaults =
] ]
rewards: [ 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 } # {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') 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={}) -> api.shouldDo = (day, dailyTask, options = {}) ->
return false unless repeat 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 o = sanitizeOptions options
selected = repeat[api.dayMapping[api.startOfDay(_.defaults {now:day}, o).day()]] day = api.startOfDay(_.defaults {now:day}, o)
return selected 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, {up:true,down:true}) if task.type is 'habit'
_.defaults(task, {history: []}) if task.type in ['habit', 'daily'] _.defaults(task, {history: []}) if task.type in ['habit', 'daily']
_.defaults(task, {completed:false}) if task.type in ['daily', 'todo'] _.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._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.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 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 # show as completed if completed (naturally) or not required for today
if type in ['todo', 'daily'] 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" classes += " completed"
else else
classes += " uncompleted" classes += " uncompleted"
@@ -1565,7 +1589,7 @@ api.wrap = (user, main=true) ->
{completed, repeat} = daily {completed, repeat} = daily
thatDay = moment(now).subtract({days: 1}) 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) _.each daily.checklist, ((box)->box.completed=false;true)
daily.completed = false daily.completed = false
return return
@@ -1592,7 +1616,7 @@ api.wrap = (user, main=true) ->
scheduleMisses = 0 scheduleMisses = 0
_.times daysMissed, (n) -> _.times daysMissed, (n) ->
thatDay = moment(now).subtract({days: n + 1}) thatDay = moment(now).subtract({days: n + 1})
if api.shouldDo(thatDay, repeat, user.preferences) if api.shouldDo(thatDay.toDate(), task, user.preferences)
scheduleMisses++ scheduleMisses++
if user.stats.buffs.stealth if user.stats.buffs.stealth
user.stats.buffs.stealth-- user.stats.buffs.stealth--

View File

@@ -6,6 +6,7 @@ Group = require("../../website/src/models/group").model
app = require("../../website/src/server") app = require("../../website/src/server")
describe "Guilds", -> describe "Guilds", ->
context "creating groups", ->
before (done) -> before (done) ->
registerNewUser -> registerNewUser ->
User.findByIdAndUpdate user._id, User.findByIdAndUpdate user._id,
@@ -15,7 +16,6 @@ describe "Guilds", ->
done() done()
, true , true
context "creating groups", ->
it "can create a public guild", (done) -> it "can create a public guild", (done) ->
request.post(baseURL + "/groups").send( request.post(baseURL + "/groups").send(
name: "TestGroup" name: "TestGroup"
@@ -53,19 +53,42 @@ describe "Guilds", ->
done() done()
, false , false
context "finding groups", -> context "get guilds", ->
it "can find a guild", (done) ->
guild = undefined guild = undefined
beforeEach (done)->
request.post(baseURL + "/groups").send( request.post(baseURL + "/groups").send(
name: "TestGroup2" name: "TestGroup2"
type: "guild" type: "guild"
).end (res) -> ).end (res) ->
guild = res.body guild = res.body
done()
it "can find a guild", (done) ->
request.get(baseURL + "/groups/" + guild._id) request.get(baseURL + "/groups/" + guild._id)
.send()
.end (res) -> .end (res) ->
expectCode res, 200 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() done()
it "can list guilds", (done) -> it "can list guilds", (done) ->
@@ -228,17 +251,24 @@ describe "Guilds", ->
describe "Public Guilds", -> describe "Public Guilds", ->
guild = undefined guild = undefined
before (done) -> before (done) ->
async.waterfall [
(cb) ->
registerNewUser ->
User.findByIdAndUpdate user._id, {$set: { "balance": 10 } }, (err, _user) ->
cb()
, true
(cb) ->
request.post(baseURL + "/groups").send( request.post(baseURL + "/groups").send(
name: "TestPublicGroup" name: "TestPublicGroup"
type: "guild" type: "guild"
privacy: "public" privacy: "public"
).end (res) -> ).end (res) ->
expectCode res, 200
guild = res.body guild = res.body
expect(guild.members.length).to.equal 1 expect(guild.members.length).to.equal 1
expect(guild.leader).to.equal user._id expect(guild.leader).to.equal user._id
#Add members to guild #Add members to guild
async.waterfall [ cb()
(cb) -> (cb) ->
registerManyUsers 15, cb registerManyUsers 15, cb
@@ -253,13 +283,14 @@ describe "Guilds", ->
callback(null, null) callback(null, null)
async.map members, joinGuild, (err, results) -> cb() async.map members, joinGuild, (err, results) -> cb()
], done ], done
context "is a member", -> context "is a member", ->
before (done) -> before (done) ->
registerNewUser -> registerNewUser ->
request.post(baseURL + "/groups/" + guild._id + "/join") request.post(baseURL + "/groups/" + guild._id + "/join")
.end -> .end (res)->
done() done()
, true , true

View File

@@ -159,7 +159,7 @@ describe 'User', ->
it 'handles perfect days', -> it 'handles perfect days', ->
user = newUser() user = newUser()
user.dailys = [] 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 = -> user.lastCron = moment().subtract(1,'days');user.fns.cron()
cron() cron()
@@ -193,7 +193,7 @@ describe 'User', ->
user.preferences.sleep = true user.preferences.sleep = true
cron = -> user.lastCron = moment().subtract(1, 'days');user.fns.cron() cron = -> user.lastCron = moment().subtract(1, 'days');user.fns.cron()
user.dailys = [] 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', -> it 'remains in the inn on cron', ->
cron() cron()
@@ -883,8 +883,9 @@ describe 'Cron', ->
before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat 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].streak = after.dailys[0].streak = 10
before.dailys[0].completed = after.dailys[0].completed = true if options.checked 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 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} after.fns.cron {now}
before.stats.mp=after.stats.mp #FIXME before.stats.mp=after.stats.mp #FIXME
switch options.expect 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; padding: 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
.formatting-help .slight-vertical-padding
clear: both clear: both
padding-top: 0.618em padding-top: 0.618em

View File

@@ -12,7 +12,7 @@ for $stage in $stages
.color-{$stage[0]}:not(.completed) .color-{$stage[0]}:not(.completed)
background-color: $stage[1] background-color: $stage[1]
border: 1px solid shade($stage[1],10%) border: 1px solid shade($stage[1],10%)
.priority-multiplier, .task-attributes, .repeat-days .priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
li li
hrpg-button-color-mixin($stage[1]) hrpg-button-color-mixin($stage[1])
button button
@@ -63,7 +63,7 @@ for $stage in $stages
color: darken($completed,30%) color: darken($completed,30%)
background-color: $completed background-color: $completed
border: 1px solid shade($completed,10%) border: 1px solid shade($completed,10%)
.priority-multiplier, .task-attributes, .repeat-days .priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
li li
hrpg-button-color-mixin($completed) hrpg-button-color-mixin($completed)
button button
@@ -410,6 +410,10 @@ form
padding: 0 0 1em padding: 0 0 1em
margin-bottom: 1em margin-bottom: 1em
button.advanced-options-toggle
display: block;
width: 100%;
background: none;
.option-title .option-title
font-size: 1em font-size: 1em
margin: 0 0 0.5em margin: 0 0 0.5em
@@ -507,7 +511,7 @@ form
form form
padding-bottom: 1em padding-bottom: 1em
.priority-multiplier, .task-attributes, .repeat-days .priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
text-align: center text-align: center
li li
@extend $hrpg-button @extend $hrpg-button
@@ -517,6 +521,7 @@ form
&:last-of-type &:last-of-type
margin-right: 0 margin-right: 0
.repeat-days .repeat-days
padding-bottom: 1em
li li
button button
min-width: 2.5em min-width: 2.5em
@@ -524,6 +529,11 @@ form
text-align: center text-align: center
@extend $hrpg-button @extend $hrpg-button
// Dailies
.dailies
.repeat-weekly
padding-bottom: 1em
// Habits task button styles (+ -) // Habits task button styles (+ -)
.habits .habits
.task-actions .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) { function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide) {
$scope.obj = User.user; // used for task-lists $scope.obj = User.user; // used for task-lists
$scope.user = User.user; $scope.user = User.user;
$scope.armoireCount = function(gear) { $scope.armoireCount = function(gear) {
return Shared.countArmoire(gear); return Shared.countArmoire(gear);
}; };
@@ -131,6 +132,19 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
*/ */
$scope._today = moment().add({days: 1}); $scope._today = moment().add({days: 1});
/*
------------------------
Dailies
------------------------
*/
$scope.openDatePicker = function($event, task) {
$event.preventDefault();
$event.stopPropagation();
task._isDatePickerOpen = !task._isDatePickerOpen;
}
/* /*
------------------------ ------------------------
Checklists Checklists
@@ -216,7 +230,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
$scope.shouldShow = function(task, list, prefs){ $scope.shouldShow = function(task, list, prefs){
if (task._editing) // never hide a task while being edited if (task._editing) // never hide a task while being edited
return true; 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) { switch (list.view) {
case "yellowred": // Habits case "yellowred": // Habits
return task.value < 1; return task.value < 1;

View File

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

View File

@@ -8,6 +8,7 @@ var mongoose = require("mongoose");
var Schema = mongoose.Schema; var Schema = mongoose.Schema;
var shared = require('../../../common'); var shared = require('../../../common');
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
// Task Schema // Task Schema
// ----------- // -----------
@@ -51,9 +52,12 @@ var checklist = [{
var DailySchema = new Schema( var DailySchema = new Schema(
_.defaults({ _.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, history: Array,
completed: {type: Boolean, 'default': false}, completed: {type: Boolean, 'default': false},
repeat: { repeat: { // used only for 'weekly' frequency,
m: {type: Boolean, 'default': true}, m: {type: Boolean, 'default': true},
t: {type: Boolean, 'default': true}, t: {type: Boolean, 'default': true},
w: {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); newTask.name = newTask.name(self.preferences.language);
}else{ }else{
newTask.text = newTask.text(self.preferences.language); newTask.text = newTask.text(self.preferences.language);
if(newTask.notes) {
newTask.notes = newTask.notes(self.preferences.language); newTask.notes = newTask.notes(self.preferences.language);
}
if(newTask.checklist){ if(newTask.checklist){
newTask.checklist = _.map(newTask.checklist, function(checklistItem){ newTask.checklist = _.map(newTask.checklist, function(checklistItem){

View File

@@ -179,8 +179,11 @@ module.exports.setupConfig = function(){
baseUrl = nconf.get('BASE_URL'); baseUrl = nconf.get('BASE_URL');
module.exports.ga = require('universal-analytics')(nconf.get('GA_ID')); 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'; 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/header/menu
include ./shared/modals/index include ./shared/modals/index
include ./shared/header/header include ./shared/header/header
include ./shared/tasks/lists include ./shared/tasks/index
include ./main/index include ./main/index
include ./options/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 span.glyphicon.glyphicon-ban-circle
=env.t('leave') =env.t('leave')
a.btn.btn-success.pull-right(ng-if=':: !isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join') 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-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') 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}}') img.pull-right(ng-show='group.logo', ng-src='{{group.logo}}')
markdown(text='group.description') markdown(text='group.description')
hr 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 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})' ng-change='set({"party.orderAscending": user.party.orderAscending})'
) )
table.table.table-striped(bindonce='group') 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 td.media
// allow leaders to ban members // 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)') a.media-object(ng-click='removeMember(group, member, true)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip')) span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a.media-body 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}} | {{member.profile.name}}
tr(ng-if='group.memberCount > group.members.length') tr(ng-if='group.memberCount > group.members.length')
td 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') tr(ng-repeat='invite in group.invites')
td.media td.media
// allow leaders to ban members // 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)') a.media-object(ng-click='removeMember(group, invite, false)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip')) span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a.media-body a.media-body
@@ -117,7 +123,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
td td
.popover.static-popover.fade.right.in.wide-popover .popover.static-popover.fade.right.in.wide-popover
.arrow .arrow
h3.popover-title {{Members.members[group.leader].profile.name}} h3.popover-title {{group.leader.profile.name}}
.popover-content .popover-content
markdown(text='group.leaderMessage') markdown(text='group.leaderMessage')
div(ng-controller='ChatCtrl') div(ng-controller='ChatCtrl')

View File

@@ -1,7 +1,7 @@
small.btn-link(ng-init='showHelp = false', ng-click='showHelp = !showHelp') small.btn-link(ng-init='showHelp = false', ng-click='showHelp = !showHelp')
| {{showHelp ? env.t('hideFormattingHelp') : env.t('showFormattingHelp')}} | {{showHelp ? env.t('hideFormattingHelp') : env.t('showFormattingHelp')}}
.formatting-help(ng-if='showHelp') .slight-vertical-padding(ng-if='showHelp')
table.table.table-striped table.table.table-striped
tr.info tr.info
td.col-xs-6 #[b=env.t('youType')] 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') .well(ng-show='selectedClass=="healer"')=env.t('healerText')
.modal-footer .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') button.btn.btn-primary(ng-disabled='!selectedClass' ng-click='changeClass(selectedClass); $close()')=env.t('select')
.pull-left!=env.t('chooseClassLearn') .pull-left!=env.t('chooseClassLearn')

View File

@@ -1,5 +1,43 @@
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 hr
tr
td
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 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 That being said, here are the new features:
tr
td
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 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 tr
td td
.promo_enchanted_armoire.pull-right .promo_enchanted_armoire.pull-right
@@ -22,12 +60,6 @@ h5 6/1/2015 - NEW EQUIPMENT: THE ENCHANTED ARMOIRE, JUNE BACKGROUNDS, AND NEW MO
h5 New Mount Positioning! 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 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 p.small.muted by Kiwibot, Lemoness, and SabreCat
hr
a(href='/static/old-news', target='_blank') Read older news
mixin oldNews
h5 6/1/2015 - JUNE MYSTERY ITEM! h5 6/1/2015 - JUNE MYSTERY ITEM!
tr tr
td 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)') li(id='task-{{::task.id}}',
// right-hand side control buttons ng-repeat='task in obj[list.type+"s"] | filterByTextAndNotes: obj.filterQuery | conditionalOrderBy: list.view=="dated":"date"',
.task-meta-controls 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 ng-form(name='taskForm')
span(ng-if='task.type=="todo" && task.date') include ./meta_controls
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 include ./task_view/index
| &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) include ./edit/index
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')
div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]') 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()}}