diff --git a/common/dist/sprites/spritesmith2.png b/common/dist/sprites/spritesmith2.png
index c448b073f5..980f2fea09 100644
Binary files a/common/dist/sprites/spritesmith2.png and b/common/dist/sprites/spritesmith2.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_0ff591.png b/common/img/sprites/spritesmith/customize/skin/skin_0ff591.png
index 9f046b4495..3ca94dce1e 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_0ff591.png and b/common/img/sprites/spritesmith/customize/skin/skin_0ff591.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_0ff591_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_0ff591_sleep.png
index c9e8e7acf3..f674a3c93f 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_0ff591_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_0ff591_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_2b43f6.png b/common/img/sprites/spritesmith/customize/skin/skin_2b43f6.png
index 1d0460856d..6ebc9f32e2 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_2b43f6.png and b/common/img/sprites/spritesmith/customize/skin/skin_2b43f6.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_2b43f6_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_2b43f6_sleep.png
index 9ad780f1ba..1cabeb8a05 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_2b43f6_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_2b43f6_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_6bd049.png b/common/img/sprites/spritesmith/customize/skin/skin_6bd049.png
index 2911881676..20e4000326 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_6bd049.png and b/common/img/sprites/spritesmith/customize/skin/skin_6bd049.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_6bd049_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_6bd049_sleep.png
index 09eecdd171..722ba42cdb 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_6bd049_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_6bd049_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_800ed0.png b/common/img/sprites/spritesmith/customize/skin/skin_800ed0.png
index 46588cb9a5..5a490c6f4c 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_800ed0.png and b/common/img/sprites/spritesmith/customize/skin/skin_800ed0.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_800ed0_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_800ed0_sleep.png
index ed46320d5c..ac0efda3c0 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_800ed0_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_800ed0_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_915533.png b/common/img/sprites/spritesmith/customize/skin/skin_915533.png
index 0d7f775d8a..823ca81764 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_915533.png and b/common/img/sprites/spritesmith/customize/skin/skin_915533.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_915533_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_915533_sleep.png
index 371f8f7b9a..340d603e98 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_915533_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_915533_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_98461a.png b/common/img/sprites/spritesmith/customize/skin/skin_98461a.png
index 8ba33e2168..a28b1c5468 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_98461a.png and b/common/img/sprites/spritesmith/customize/skin/skin_98461a.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_98461a_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_98461a_sleep.png
index ee6ffa2199..2741afbe74 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_98461a_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_98461a_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_c06534.png b/common/img/sprites/spritesmith/customize/skin/skin_c06534.png
index 5a0e350242..ef3ebe76f0 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_c06534.png and b/common/img/sprites/spritesmith/customize/skin/skin_c06534.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_c06534_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_c06534_sleep.png
index 09ac494f08..ddbb7519f0 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_c06534_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_c06534_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc.png b/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc.png
index b3a0acf388..a9d1b09425 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc.png and b/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc_sleep.png
index 5a8d12c052..eb505a4db8 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_c3e1dc_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7.png b/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7.png
index ecf379b8ad..9ece142678 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7.png and b/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7_sleep.png
index 40bd6dd25a..8ce9331d40 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_d7a9f7_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_ddc994.png b/common/img/sprites/spritesmith/customize/skin/skin_ddc994.png
index c084a9e760..e55ccd274d 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_ddc994.png and b/common/img/sprites/spritesmith/customize/skin/skin_ddc994.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_ddc994_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_ddc994_sleep.png
index 920727de5b..20ee65d4f9 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_ddc994_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_ddc994_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_ea8349.png b/common/img/sprites/spritesmith/customize/skin/skin_ea8349.png
index 470723ed9f..74ec2b5c15 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_ea8349.png and b/common/img/sprites/spritesmith/customize/skin/skin_ea8349.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_ea8349_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_ea8349_sleep.png
index 1484d376f8..8e8c089749 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_ea8349_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_ea8349_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_eb052b.png b/common/img/sprites/spritesmith/customize/skin/skin_eb052b.png
index ede562fb75..ffa04f3e26 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_eb052b.png and b/common/img/sprites/spritesmith/customize/skin/skin_eb052b.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_eb052b_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_eb052b_sleep.png
index 21e2dc8ae2..ff384460b3 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_eb052b_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_eb052b_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_f5a76e.png b/common/img/sprites/spritesmith/customize/skin/skin_f5a76e.png
index 35d2f7f08e..0e7b3bbfb9 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_f5a76e.png and b/common/img/sprites/spritesmith/customize/skin/skin_f5a76e.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_f5a76e_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_f5a76e_sleep.png
index 64a078a008..5621d1001a 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_f5a76e_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_f5a76e_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_f5d70f.png b/common/img/sprites/spritesmith/customize/skin/skin_f5d70f.png
index eb9067a981..4ba5d3193e 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_f5d70f.png and b/common/img/sprites/spritesmith/customize/skin/skin_f5d70f.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_f5d70f_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_f5d70f_sleep.png
index b5fb256d22..5cb072899e 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_f5d70f_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_f5d70f_sleep.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_f69922.png b/common/img/sprites/spritesmith/customize/skin/skin_f69922.png
index 2ac2ef62a8..071299b891 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_f69922.png and b/common/img/sprites/spritesmith/customize/skin/skin_f69922.png differ
diff --git a/common/img/sprites/spritesmith/customize/skin/skin_f69922_sleep.png b/common/img/sprites/spritesmith/customize/skin/skin_f69922_sleep.png
index 786e8fbd3c..0d12e4afb4 100644
Binary files a/common/img/sprites/spritesmith/customize/skin/skin_f69922_sleep.png and b/common/img/sprites/spritesmith/customize/skin/skin_f69922_sleep.png differ
diff --git a/common/locales/en/character.json b/common/locales/en/character.json
index 885c34799e..b6909e6c63 100644
--- a/common/locales/en/character.json
+++ b/common/locales/en/character.json
@@ -49,7 +49,7 @@
"classBonusText": "Your class (Warrior, if you haven't unlocked or selected another class) uses its own equipment more effectively than gear from other classes. Equipped gear from your current class gets a 50% boost to the attribute bonus it grants.",
"classEquipBonus": "Class Bonus",
"battleGear": "Battle Gear",
- "battleGearText": "This is the gear you wear into battle, it affects numbers when interacting with your tasks.",
+ "battleGearText": "This is the gear you wear into battle; it affects numbers when interacting with your tasks.",
"costume": "Costume",
"costumeText": "If you prefer the look of other gear to what you have equipped, check the \"Use Costume\" box to visually don a costume while wearing your battle gear underneath.",
"useCostume": "Use Costume",
diff --git a/common/locales/en/defaultTasks.json b/common/locales/en/defaultTasks.json
index 48e405ab6f..cf7e179494 100644
--- a/common/locales/en/defaultTasks.json
+++ b/common/locales/en/defaultTasks.json
@@ -1,7 +1,7 @@
{
"defaultTaskNotes": " ",
"defaultHabit1Text": "Productive Work (Click the pencil to edit)",
- "defaultHabit1Notes": "Sample Good Habits: + Eat a vegetable +15 minutes productive work",
+ "defaultHabit1Notes": "Sample Good Habits: + Eat a vegetable + 15 minutes productive work",
"defaultHabit2Text": "Eat Junk Food (Click the pencil to edit)",
"defaultHabit2Notes": "Sample Bad Habits: - Smoke - Procrastinate",
diff --git a/common/locales/en/generic.json b/common/locales/en/generic.json
index c1aa3c2118..d92d82a524 100644
--- a/common/locales/en/generic.json
+++ b/common/locales/en/generic.json
@@ -70,7 +70,6 @@
"lastLoggedIn": "- Last logged in",
"notPorted": "This feature is not yet ported from the original site.",
"buyThis": "Buy this <%= text %> with <%= price %> of your <%= gems %> Gems?",
- "untilNoFace": "Until we add Facebook, use your UUID and API Token to log in (found at https://habitrpg.com > Options > Settings).",
"noReachServer": "Server not currently reachable, try again later",
"errorUpCase": "ERROR:",
"newPassSent": "New password sent.",
diff --git a/common/locales/en/groups.json b/common/locales/en/groups.json
index ce6a86e786..688683fc94 100644
--- a/common/locales/en/groups.json
+++ b/common/locales/en/groups.json
@@ -39,6 +39,8 @@
"editGroup": "Edit Group",
"newGroupName": "<%= groupType %> Name",
"groupName": "Group Name",
+ "groupLeader": "Group Leader",
+ "groupID": "Group ID",
"groupDescr": "Description shown in public Guilds list (Markdown OK)",
"logoUrl": "Logo URL",
"assignLeader": "Assign Group Leader",
diff --git a/common/locales/en/quests.json b/common/locales/en/quests.json
index c2aada5de6..41fa3627f0 100644
--- a/common/locales/en/quests.json
+++ b/common/locales/en/quests.json
@@ -3,7 +3,7 @@
"quest": "quest",
"completed": "Completed!",
"youReceived": "You've Received",
- "questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Options > Social > Party.",
+ "questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Social > Party.",
"inviteParty": "Invite Party",
"questInvitation": "Quest Invitation: ",
"questInvitationTitle": "Quest Invitation",
diff --git a/common/locales/en/tasks.json b/common/locales/en/tasks.json
index 2353e9de2b..3787a391df 100644
--- a/common/locales/en/tasks.json
+++ b/common/locales/en/tasks.json
@@ -36,6 +36,11 @@
"newDailyBulk": "New Dailies (one per line)",
"streakCounter": "Streak Counter",
"repeat": "Repeat",
+ "repeatEvery": "Repeat Every",
+ "repeatDays": "Every X Days",
+ "repeatWeek": "On Certain Days of the Week",
+ "day": "Day",
+ "days": "Days",
"restoreStreak": "Restore Streak",
"todos": "To-Dos",
"newTodo": "New To-Do",
@@ -60,6 +65,9 @@
"clearTags": "Clear",
"hideTags": "Hide",
"showTags": "Show",
+ "startDate": "Start Date",
+ "startDateHelpTitle": "When should this task start?",
+ "startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
"streakName": "Streak Achievements",
"streakText": "Has performed <%= streaks %> 21-day streaks on Dailies",
"streakSingular": "Streaker",
@@ -81,17 +89,17 @@
"dailiesRestingInInn": "You're Resting in the Inn! Your Dailies will NOT hurt you tonight, but they WILL still refresh every day. If you're in a quest, you won't deal damage/collect items until you check out of the Inn, but you can still be injured by a Boss if your Party mates skip their own Dailies.",
"habitHelp1": "Good Habits are things that you do often. They award Gold and Experience every time you click the <%= plusIcon %>.",
"habitHelp2": "Bad Habits are things you want to avoid doing. They remove Health every time you click the <%= minusIcon %>.",
- "habitHelp3": "For inspiration, check out <%= linkStart %>these sample Habits<%= linkEnd %>.",
+ "habitHelp3": "For inspiration, check out these sample Habits!",
"newbieGuild": "More questions? Ask in the <%= linkStart %>Newbies Guild<%= linkEnd %>!",
"dailyHelp1": "Dailies repeat <%= emphasisStart %>every day<%= emphasisEnd %> that they are active. Click the <%= pencilIcon %> to change the days a Daily is active.",
"dailyHelp2": "If you don't complete active Dailies, you lose Health when your day rolls over.",
"dailyHelp3": "Dailies turn <%= emphasisStart %>redder<%= emphasisEnd %> when you miss them, and <%= emphasisStart %>bluer<%= emphasisEnd %> when you complete them. The redder the Daily, the more it will reward you... or hurt you.",
"dailyHelp4": "To change when your day rolls over, go to <%= linkStart %> Settings > Site<%= linkEnd %> > Custom Day Start.",
- "dailyHelp5": "For inspiration, check out these <%= linkStart %>sample Dailies<%= linkEnd %>!",
+ "dailyHelp5": "For inspiration, check out these sample Dailies!",
"toDoHelp1": "To-Dos start yellow, and get redder (more valuable) the longer it takes to complete them.",
"toDoHelp2": "To-Dos never hurt you! They only award Gold and Experience.",
"toDoHelp3": "Breaking a To-Do down into a checklist of smaller items will make it less scary, and will increase your points!",
- "toDoHelp4": "For inspiration, check out these <%= linkStart %>sample To-Dos<%= linkEnd %>!",
+ "toDoHelp4": "For inspiration, check out these sample To-Dos!",
"rewardHelp1": "The Equipment you buy for your avatar is stored in <%= linkStart %>Inventory > Equipment<%= linkEnd %>.",
"rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here during World Events.",
diff --git a/common/script/content.coffee b/common/script/content.coffee
index cef86206aa..30a0760b87 100644
--- a/common/script/content.coffee
+++ b/common/script/content.coffee
@@ -1877,9 +1877,9 @@ _.each api.subscriptionBlocks, (b,k)->b.key = k
repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true}
api.userDefaults =
habits: [
- {type: 'habit', text: t('defaultHabit1Text'), notes: t('defaultTaskNotes'), value: 0, up: true, down: false, attribute: 'per' }
- {type: 'habit', text: t('defaultHabit2Text'), notes: t('defaultTaskNotes'), value: 0, up: false, down: true, attribute: 'str'}
- {type: 'habit', text: t('defaultHabit3Text'), notes: t('defaultTaskNotes'), value: 0, up: true, down: true, attribute: 'str'}
+ {type: 'habit', text: t('defaultHabit1Text'), value: 0, up: true, down: false, attribute: 'per' }
+ {type: 'habit', text: t('defaultHabit2Text'), value: 0, up: false, down: true, attribute: 'str'}
+ {type: 'habit', text: t('defaultHabit3Text'), value: 0, up: true, down: true, attribute: 'str'}
]
dailys: [
@@ -1898,7 +1898,7 @@ api.userDefaults =
]
rewards: [
- {type: 'reward', text: t('defaultReward1Text'), notes: t('defaultTaskNotes'), value: 10 }
+ {type: 'reward', text: t('defaultReward1Text'), value: 10 }
# {type: 'reward', text: t('defaultReward2Text'), notes: t('defaultReward2Notes'), value: 10 }
]
diff --git a/common/script/index.coffee b/common/script/index.coffee
index 5ab1c09699..9d70016185 100644
--- a/common/script/index.coffee
+++ b/common/script/index.coffee
@@ -72,13 +72,37 @@ api.daysSince = (yesterday, options = {}) ->
Math.abs api.startOfDay(_.defaults {now:yesterday}, o).diff(api.startOfDay(_.defaults {now:o.now}, o), 'days')
###
- Should the user do this taks on this date, given the task's repeat options and user.preferences.dayStart?
+ Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart?
###
-api.shouldDo = (day, repeat, options={}) ->
- return false unless repeat
+api.shouldDo = (day, dailyTask, options = {}) ->
+ return false unless dailyTask.type == 'daily' && dailyTask.repeat
+ if !dailyTask.startDate
+ dailyTask.startDate = moment().toDate()
+ if dailyTask.startDate instanceof String
+ dailyTask.startDate = moment(dailyTask.startDate).toDate()
o = sanitizeOptions options
- selected = repeat[api.dayMapping[api.startOfDay(_.defaults {now:day}, o).day()]]
- return selected
+ day = api.startOfDay(_.defaults {now:day}, o)
+ dayOfWeekNum = day.day() # e.g. 1 for Monday if week starts on Mon
+
+ # check if event is today or in the future
+ hasStartedCheck = day >= api.startOfDay(_.defaults {now:dailyTask.startDate}, o)
+
+ if dailyTask.frequency == 'daily'
+ daysSinceTaskStart = api.numDaysApart(day.startOf('day'), dailyTask.startDate, o)
+ everyXCheck = (daysSinceTaskStart % dailyTask.everyX == 0)
+ return everyXCheck && hasStartedCheck
+ else if dailyTask.frequency == 'weekly'
+ dayOfWeekCheck = dailyTask.repeat[api.dayMapping[dayOfWeekNum]]
+ return dayOfWeekCheck && hasStartedCheck
+ else
+ # unexpected frequency string
+ return false
+
+api.numDaysApart = (day1, day2, o) ->
+ startOfDay1 = api.startOfDay(_.defaults {now:day1}, o)
+ startOfDay2 = api.startOfDay(_.defaults {now:day2}, o)
+ numDays = Math.abs(startOfDay1.diff(startOfDay2, 'days'))
+ return numDays
###
------------------------------------------------------
@@ -211,7 +235,7 @@ api.taskDefaults = (task={}) ->
_.defaults(task, {up:true,down:true}) if task.type is 'habit'
_.defaults(task, {history: []}) if task.type in ['habit', 'daily']
_.defaults(task, {completed:false}) if task.type in ['daily', 'todo']
- _.defaults(task, {streak:0, repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}}) if task.type is 'daily'
+ _.defaults(task, {streak:0, repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}}, startDate: new Date(), everyX: 1, frequency: 'weekly') if task.type is 'daily'
task._id = task.id # may need this for TaskSchema if we go back to using it, see http://goo.gl/a5irq4
task.value ?= if task.type is 'reward' then 10 else 0
task.priority = 1 unless _.isNumber(task.priority) # hotfix for apiv1. once we're off apiv1, we can remove this
@@ -281,7 +305,7 @@ api.taskClasses = (task, filters=[], dayStart=0, lastCron=+new Date, showComplet
# show as completed if completed (naturally) or not required for today
if type in ['todo', 'daily']
- if completed or (type is 'daily' and !api.shouldDo(+new Date, task.repeat, {dayStart}))
+ if completed or (type is 'daily' and !api.shouldDo(+new Date, task, {dayStart}))
classes += " completed"
else
classes += " uncompleted"
@@ -1565,7 +1589,7 @@ api.wrap = (user, main=true) ->
{completed, repeat} = daily
thatDay = moment(now).subtract({days: 1})
- if api.shouldDo(thatDay, repeat, user.preferences) || completed
+ if api.shouldDo(thatDay.toDate(), daily, user.preferences) || completed
_.each daily.checklist, ((box)->box.completed=false;true)
daily.completed = false
return
@@ -1592,7 +1616,7 @@ api.wrap = (user, main=true) ->
scheduleMisses = 0
_.times daysMissed, (n) ->
thatDay = moment(now).subtract({days: n + 1})
- if api.shouldDo(thatDay, repeat, user.preferences)
+ if api.shouldDo(thatDay.toDate(), task, user.preferences)
scheduleMisses++
if user.stats.buffs.stealth
user.stats.buffs.stealth--
diff --git a/test/api/groups.coffee b/test/api/groups.coffee
index 40de0e4077..6c2d922476 100644
--- a/test/api/groups.coffee
+++ b/test/api/groups.coffee
@@ -6,16 +6,16 @@ Group = require("../../website/src/models/group").model
app = require("../../website/src/server")
describe "Guilds", ->
- before (done) ->
- registerNewUser ->
- User.findByIdAndUpdate user._id,
- $set:
- "balance": 10
- , (err, _user) ->
- done()
- , true
-
context "creating groups", ->
+ before (done) ->
+ registerNewUser ->
+ User.findByIdAndUpdate user._id,
+ $set:
+ "balance": 10
+ , (err, _user) ->
+ done()
+ , true
+
it "can create a public guild", (done) ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
@@ -53,19 +53,42 @@ describe "Guilds", ->
done()
, false
- context "finding groups", ->
- it "can find a guild", (done) ->
- guild = undefined
+ context "get guilds", ->
+ guild = undefined
+
+ beforeEach (done)->
request.post(baseURL + "/groups").send(
name: "TestGroup2"
type: "guild"
).end (res) ->
guild = res.body
- request.get(baseURL + "/groups/" + guild._id)
- .send()
+ done()
+
+ it "can find a guild", (done) ->
+ request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expectCode res, 200
- expect(guild._id).to.equal res.body._id
+ expect(res.body._id).to.equal res.body._id
+ done()
+
+ it "transforms members array to an arrray of user objects", (done) ->
+ request.get(baseURL + "/groups/" + guild._id)
+ .end (res) ->
+ expectCode res, 200
+ members = res.body.members
+ # @TODO: would be more instructive if it had more members in guild :(
+ _(members).each (member) ->
+ expect(member).to.be.an 'object'
+ expect(member.profile.name).to.exist
+ done()
+
+ it "transforms leader id to a user object", (done) ->
+ request.get(baseURL + "/groups/" + guild._id)
+ .end (res) ->
+ expectCode res, 200
+ leader = res.body.leader
+ expect(leader).to.be.an 'object'
+ expect(leader.profile.name).to.exist
done()
it "can list guilds", (done) ->
@@ -228,38 +251,46 @@ describe "Guilds", ->
describe "Public Guilds", ->
guild = undefined
before (done) ->
- request.post(baseURL + "/groups").send(
- name: "TestPublicGroup"
- type: "guild"
- privacy: "public"
- ).end (res) ->
- expectCode res, 200
- guild = res.body
- expect(guild.members.length).to.equal 1
- expect(guild.leader).to.equal user._id
- #Add members to guild
- async.waterfall [
- (cb) ->
- registerManyUsers 15, cb
+ async.waterfall [
+ (cb) ->
+ registerNewUser ->
+ User.findByIdAndUpdate user._id, {$set: { "balance": 10 } }, (err, _user) ->
+ cb()
+ , true
+ (cb) ->
+ request.post(baseURL + "/groups").send(
+ name: "TestPublicGroup"
+ type: "guild"
+ privacy: "public"
+ ).end (res) ->
+ guild = res.body
+ expect(guild.members.length).to.equal 1
+ expect(guild.leader).to.equal user._id
+ #Add members to guild
+ cb()
- (_members, cb) ->
- members = _members
+ (cb) ->
+ registerManyUsers 15, cb
- joinGuild = (member, callback) ->
- request.post(baseURL + "/groups/" + guild._id + "/join")
- .set("X-API-User", member._id)
- .set("X-API-Key", member.apiToken)
- .end ->
- callback(null, null)
+ (_members, cb) ->
+ members = _members
- async.map members, joinGuild, (err, results) -> cb()
- ], done
+ joinGuild = (member, callback) ->
+ request.post(baseURL + "/groups/" + guild._id + "/join")
+ .set("X-API-User", member._id)
+ .set("X-API-Key", member.apiToken)
+ .end ->
+ callback(null, null)
+
+ async.map members, joinGuild, (err, results) -> cb()
+
+ ], done
context "is a member", ->
before (done) ->
registerNewUser ->
request.post(baseURL + "/groups/" + guild._id + "/join")
- .end ->
+ .end (res)->
done()
, true
diff --git a/test/common/algos.mocha.coffee b/test/common/algos.mocha.coffee
index 1f02d94628..44ff5d0f72 100644
--- a/test/common/algos.mocha.coffee
+++ b/test/common/algos.mocha.coffee
@@ -159,7 +159,7 @@ describe 'User', ->
it 'handles perfect days', ->
user = newUser()
user.dailys = []
- _.times 3, ->user.dailys.push shared.taskDefaults({type:'daily'})
+ _.times 3, ->user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')})
cron = -> user.lastCron = moment().subtract(1,'days');user.fns.cron()
cron()
@@ -193,7 +193,7 @@ describe 'User', ->
user.preferences.sleep = true
cron = -> user.lastCron = moment().subtract(1, 'days');user.fns.cron()
user.dailys = []
- _.times 2, -> user.dailys.push shared.taskDefaults({type:'daily'})
+ _.times 2, -> user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')})
it 'remains in the inn on cron', ->
cron()
@@ -883,8 +883,9 @@ describe 'Cron', ->
before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat
before.dailys[0].streak = after.dailys[0].streak = 10
before.dailys[0].completed = after.dailys[0].completed = true if options.checked
+ before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days')
if options.shouldDo
- expect(shared.shouldDo(now, options.repeat, {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
+ expect(shared.shouldDo(now.toDate(), after.dailys[0], {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
after.fns.cron {now}
before.stats.mp=after.stats.mp #FIXME
switch options.expect
diff --git a/test/common/dailies.coffee b/test/common/dailies.coffee
new file mode 100644
index 0000000000..d26c8d012e
--- /dev/null
+++ b/test/common/dailies.coffee
@@ -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
diff --git a/website/public/css/game-pane.styl b/website/public/css/game-pane.styl
index 1b6b1976ad..ea2729910d 100644
--- a/website/public/css/game-pane.styl
+++ b/website/public/css/game-pane.styl
@@ -21,7 +21,7 @@
padding: 1em;
margin-bottom: 0.5em;
-.formatting-help
+.slight-vertical-padding
clear: both
padding-top: 0.618em
diff --git a/website/public/css/tasks.styl b/website/public/css/tasks.styl
index 59cd9271c4..7e98df0f9e 100644
--- a/website/public/css/tasks.styl
+++ b/website/public/css/tasks.styl
@@ -12,7 +12,7 @@ for $stage in $stages
.color-{$stage[0]}:not(.completed)
background-color: $stage[1]
border: 1px solid shade($stage[1],10%)
- .priority-multiplier, .task-attributes, .repeat-days
+ .priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
li
hrpg-button-color-mixin($stage[1])
button
@@ -63,7 +63,7 @@ for $stage in $stages
color: darken($completed,30%)
background-color: $completed
border: 1px solid shade($completed,10%)
- .priority-multiplier, .task-attributes, .repeat-days
+ .priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
li
hrpg-button-color-mixin($completed)
button
@@ -410,6 +410,10 @@ form
padding: 0 0 1em
margin-bottom: 1em
+ button.advanced-options-toggle
+ display: block;
+ width: 100%;
+ background: none;
.option-title
font-size: 1em
margin: 0 0 0.5em
@@ -507,7 +511,7 @@ form
form
padding-bottom: 1em
- .priority-multiplier, .task-attributes, .repeat-days
+ .priority-multiplier, .task-attributes, .repeat-days, .repeat-frequency
text-align: center
li
@extend $hrpg-button
@@ -517,6 +521,7 @@ form
&:last-of-type
margin-right: 0
.repeat-days
+ padding-bottom: 1em
li
button
min-width: 2.5em
@@ -524,6 +529,11 @@ form
text-align: center
@extend $hrpg-button
+// Dailies
+.dailies
+ .repeat-weekly
+ padding-bottom: 1em
+
// Habits – task button styles (+ -)
.habits
.task-actions
diff --git a/website/public/emails/images/PROMO-Enchanted-Armoire-v1.png b/website/public/emails/images/PROMO-Enchanted-Armoire-v1.png
new file mode 100644
index 0000000000..9a42b70e42
Binary files /dev/null and b/website/public/emails/images/PROMO-Enchanted-Armoire-v1.png differ
diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js
index 3bb0df8c31..634847ad1a 100644
--- a/website/public/js/controllers/tasksCtrl.js
+++ b/website/public/js/controllers/tasksCtrl.js
@@ -4,6 +4,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide) {
$scope.obj = User.user; // used for task-lists
$scope.user = User.user;
+
$scope.armoireCount = function(gear) {
return Shared.countArmoire(gear);
};
@@ -131,6 +132,19 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
*/
$scope._today = moment().add({days: 1});
+ /*
+ ------------------------
+ Dailies
+ ------------------------
+ */
+
+ $scope.openDatePicker = function($event, task) {
+ $event.preventDefault();
+ $event.stopPropagation();
+
+ task._isDatePickerOpen = !task._isDatePickerOpen;
+ }
+
/*
------------------------
Checklists
@@ -216,7 +230,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
$scope.shouldShow = function(task, list, prefs){
if (task._editing) // never hide a task while being edited
return true;
- var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task.repeat, prefs) : true;
+ var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task, prefs) : true;
switch (list.view) {
case "yellowred": // Habits
return task.value < 1;
diff --git a/website/src/controllers/groups.js b/website/src/controllers/groups.js
index 876773491a..acb893b1ec 100644
--- a/website/src/controllers/groups.js
+++ b/website/src/controllers/groups.js
@@ -40,6 +40,7 @@ var populateQuery = function(type, q, additionalFields){
q.populate('members', partyFields + (additionalFields ? (' ' + additionalFields) : ''));
else
q.populate(guildPopulate);
+ q.populate('leader', nameFields);
q.populate('invites', nameFields);
q.populate({
path: 'challenges',
diff --git a/website/src/models/task.js b/website/src/models/task.js
index 2646263e80..8ae24e353a 100644
--- a/website/src/models/task.js
+++ b/website/src/models/task.js
@@ -8,6 +8,7 @@ var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var shared = require('../../../common');
var _ = require('lodash');
+var moment = require('moment');
// Task Schema
// -----------
@@ -50,10 +51,13 @@ var checklist = [{
var DailySchema = new Schema(
_.defaults({
- type: {type:String, 'default': 'daily'},
+ type: {type: String, 'default': 'daily'},
+ frequency: {type: String, 'default': 'weekly', enum: ['daily', 'weekly']},
+ everyX: {type: Number, 'default': 1}, // e.g. once every X weeks
+ startDate: {type: Date, 'default': moment().startOf('day').toDate()},
history: Array,
completed: {type: Boolean, 'default': false},
- repeat: {
+ repeat: { // used only for 'weekly' frequency,
m: {type: Boolean, 'default': true},
t: {type: Boolean, 'default': true},
w: {type: Boolean, 'default': true},
diff --git a/website/src/models/user.js b/website/src/models/user.js
index b24acfd886..634bb61f07 100644
--- a/website/src/models/user.js
+++ b/website/src/models/user.js
@@ -406,7 +406,7 @@ var UserSchema = new Schema({
rewards: {type:[TaskSchemas.RewardSchema]},
extra: Schema.Types.Mixed,
-
+
pushDevices: {type: [{
regId: {type: String},
type: {type: String}
@@ -459,7 +459,9 @@ UserSchema.pre('save', function(next) {
newTask.name = newTask.name(self.preferences.language);
}else{
newTask.text = newTask.text(self.preferences.language);
- newTask.notes = newTask.notes(self.preferences.language);
+ if(newTask.notes) {
+ newTask.notes = newTask.notes(self.preferences.language);
+ }
if(newTask.checklist){
newTask.checklist = _.map(newTask.checklist, function(checklistItem){
diff --git a/website/src/utils.js b/website/src/utils.js
index 85ffb664ec..beb093e28e 100644
--- a/website/src/utils.js
+++ b/website/src/utils.js
@@ -179,8 +179,11 @@ module.exports.setupConfig = function(){
baseUrl = nconf.get('BASE_URL');
module.exports.ga = require('universal-analytics')(nconf.get('GA_ID'));
- var mixpanel = require('mixpanel')
- module.exports.mixpanel = mixpanel.init(nconf.get('MP_ID'));
+
+ var mixpanel = isProd && require('mixpanel');
+ module.exports.mixpanel = mixpanel
+ ? mixpanel.init(nconf.get('MP_ID'))
+ : { track: function() {} };
};
var algorithm = 'aes-256-ctr';
diff --git a/website/views/index.jade b/website/views/index.jade
index cf70ebccdf..52d464c1b0 100644
--- a/website/views/index.jade
+++ b/website/views/index.jade
@@ -38,7 +38,7 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a
include ./shared/header/menu
include ./shared/modals/index
include ./shared/header/header
- include ./shared/tasks/lists
+ include ./shared/tasks/index
include ./main/index
include ./options/index
diff --git a/website/views/options/social/group.jade b/website/views/options/social/group.jade
index 9c35139c58..0765bcd7d7 100644
--- a/website/views/options/social/group.jade
+++ b/website/views/options/social/group.jade
@@ -22,7 +22,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
span.glyphicon.glyphicon-ban-circle
=env.t('leave')
a.btn.btn-success.pull-right(ng-if=':: !isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join')
- span(ng-if='group.leader == user.id')
+ span(ng-if='group.leader._id == user.id')
button.btn.btn-sm.btn-primary.pull-right(ng-click='save(group)', ng-show='group._editing')=env.t('save')
button.btn.btn-sm.btn-default.pull-right(ng-click='group._editing = true', ng-hide='group._editing')=env.t('editGroup')
@@ -51,7 +51,13 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
img.pull-right(ng-show='group.logo', ng-src='{{group.logo}}')
markdown(text='group.description')
hr
- small.muted Group ID: {{group._id}}
+ p=env.t('groupLeader')
+ |:
+ a(class='badge badge-info', ng-click='clickMember(group.leader._id, true)')
+ | {{group.leader.profile.name}}
+ .slight-vertical-padding
+ small.muted=env.t('groupID')
+ |: {{group._id}}
include ./challenge-box
@@ -78,14 +84,14 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
ng-change='set({"party.orderAscending": user.party.orderAscending})'
)
table.table.table-striped(bindonce='group')
- tr(ng-repeat='member in group.members track by member._id')
+ tr(ng-repeat='member in group.members track by member._id' ng-if='member._id != group.leader._id')
td.media
// allow leaders to ban members
- div.pull-left(ng-show='group.leader == user.id && user.id!=member._id')
+ div.pull-left(ng-show='group.leader._id == user.id')
a.media-object(ng-click='removeMember(group, member, true)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a.media-body
- span(ng-class='{"badge badge-info": group.leader==member._id}', ng-click='clickMember(member._id, true)')
+ span(ng-click='clickMember(member._id, true)')
| {{member.profile.name}}
tr(ng-if='group.memberCount > group.members.length')
td
@@ -98,7 +104,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
tr(ng-repeat='invite in group.invites')
td.media
// allow leaders to ban members
- div.pull-left(ng-show='group.leader == user.id')
+ div.pull-left(ng-show='group.leader._id == user.id')
a.media-object(ng-click='removeMember(group, invite, false)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a.media-body
@@ -117,7 +123,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
td
.popover.static-popover.fade.right.in.wide-popover
.arrow
- h3.popover-title {{Members.members[group.leader].profile.name}}
+ h3.popover-title {{group.leader.profile.name}}
.popover-content
markdown(text='group.leaderMessage')
div(ng-controller='ChatCtrl')
diff --git a/website/views/shared/formatting-help.jade b/website/views/shared/formatting-help.jade
index c4500e03b3..9ad945093e 100644
--- a/website/views/shared/formatting-help.jade
+++ b/website/views/shared/formatting-help.jade
@@ -1,7 +1,7 @@
small.btn-link(ng-init='showHelp = false', ng-click='showHelp = !showHelp')
| {{showHelp ? env.t('hideFormattingHelp') : env.t('showFormattingHelp')}}
-.formatting-help(ng-if='showHelp')
+.slight-vertical-padding(ng-if='showHelp')
table.table.table-striped
tr.info
td.col-xs-6 #[b=env.t('youType')]
diff --git a/website/views/shared/modals/classes.jade b/website/views/shared/modals/classes.jade
index b199cd0e83..d9fdaa054d 100644
--- a/website/views/shared/modals/classes.jade
+++ b/website/views/shared/modals/classes.jade
@@ -67,6 +67,7 @@ script(type='text/ng-template', id='modals/chooseClass.html')
.well(ng-show='selectedClass=="healer"')=env.t('healerText')
.modal-footer
- button.btn.btn-danger(ng-click='user.ops.disableClasses({}); $close()', popover-placement='top', popover-trigger='mouseenter', popover=env.t('optOutText'))=env.t('optOut')
+ span(popover-placement='left', popover-trigger='mouseenter', popover=env.t('optOutText'))
+ button.btn.btn-danger(ng-click='user.ops.disableClasses({}); $close()')=env.t('optOut')
button.btn.btn-primary(ng-disabled='!selectedClass' ng-click='changeClass(selectedClass); $close()')=env.t('select')
.pull-left!=env.t('chooseClassLearn')
diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade
index ec2703ca20..1b86e5b522 100644
--- a/website/views/shared/new-stuff.jade
+++ b/website/views/shared/new-stuff.jade
@@ -1,33 +1,65 @@
-h5 6/1/2015 - NEW EQUIPMENT: THE ENCHANTED ARMOIRE, JUNE BACKGROUNDS, AND NEW MOUNT POSITIONING!
+h5 6/11/2015 - REPEATING TASKS, START DATE, AND MOBILE APP UPDATES!
+ p
+ br
+ p.small.muted by Blade and fallenpanda
hr
tr
td
- .promo_enchanted_armoire.pull-right
- h5 New Equipment: The Enchanted Armoire!
- p Now after you achieve Ultimate Gear, you'll unlock a new Reward: THE ENCHANTED ARMOIRE!
+ h5 New Repeat Option for Dailies
+ p Dailies now have a new Advanced Option: Repeat Every X Days. You've wanted this feature for a long time, and it's finally here!
br
- p Click on the Enchanted Armoire, a 100 GP Reward in the Rewards Column, for a random chance at special Equipment! It may also give you random XP or food items. We'll be adding new equipment to it every month, but even when you've exhausted the current supply, you can keep clicking for a chance at food and XP.
+ p First, please note that this new option is OPT-IN only. We won't make any changes to your preexisting Dailies without you knowing it. We wouldn't do that!
br
- p Now go spend all that accumulated Gold! May the Random Number Generator smile upon you...
- p.small.muted by Lemoness and SabreCat
- p.small.muted Art by Kiwibot, Starsystemic, UncommonCriminal, Zoebeagle, and Andrews38
+ p That being said, here are the new features:
tr
td
- .background_island_waterfalls.pull-right
- h5 June Backgrounds Revealed
- p There are three new avatar backgrounds in the Background Shop! Now your avatar can paddle a Drifting Raft, float through a sea of Shimmery Bubbles, or picnic near Island Waterfalls!
- p.small.muted by (in order): Teto is Great, beffymaroo, and UncommonCriminal
+ h5 Repeating Tasks
+ p Use the "Every X Days" function under Dailies Advanced Options to create tasks that repeat after a certain number of days have passed, whether every 2 days, every 15 days, every 30 days... You choose the number that works for you!
+ br
+ p These Dailies are only due on those given dates. Need to pay your rent every 30 days? Take medicine every other day? Water your plants every 4 days? No longer a problem.
tr
td
- h5 New Mount Positioning!
- p The mount positioning has been fixed for all the base mounts where it looked like the avatar was riding extreme sidesaddle. Now avatars sit properly, no longer clinging to the sides of their mounts for dear life.
- p.small.muted by Kiwibot, Lemoness, and SabreCat
+ h5 Start Date
+ p Dailies now have a Start Date. They will not be due before this date. This means that if you want to add a new Daily while you're thinking about it, but not have it be due until later, you can achieve that by setting a future Start Date!
+ tr
+ td
+ h5 Mobile App Updates
+ p New Android and iOS 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 Data Display Tool 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 the Newbies Guild!
hr
a(href='/static/old-news', target='_blank') Read older news
mixin oldNews
+ h5 6/1/2015 - NEW EQUIPMENT: THE ENCHANTED ARMOIRE, JUNE BACKGROUNDS, AND NEW MOUNT POSITIONING!
+ tr
+ td
+ .promo_enchanted_armoire.pull-right
+ h5 New Equipment: The Enchanted Armoire!
+ p Now after you achieve Ultimate Gear, you'll unlock a new Reward: THE ENCHANTED ARMOIRE!
+ br
+ p Click on the Enchanted Armoire, a 100 GP Reward in the Rewards Column, for a random chance at special Equipment! It may also give you random XP or food items. We'll be adding new equipment to it every month, but even when you've exhausted the current supply, you can keep clicking for a chance at food and XP.
+ br
+ p Now go spend all that accumulated Gold! May the Random Number Generator smile upon you...
+ p.small.muted by Lemoness and SabreCat
+ p.small.muted Art by Kiwibot, Starsystemic, UncommonCriminal, Zoebeagle, and Andrews38
+ tr
+ td
+ .background_island_waterfalls.pull-right
+ h5 June Backgrounds Revealed
+ p There are three new avatar backgrounds in the Background Shop! Now your avatar can paddle a Drifting Raft, float through a sea of Shimmery Bubbles, or picnic near Island Waterfalls!
+ p.small.muted by (in order): Teto is Great, beffymaroo, and UncommonCriminal
+ tr
+ td
+ h5 New Mount Positioning!
+ p The mount positioning has been fixed for all the base mounts where it looked like the avatar was riding extreme sidesaddle. Now avatars sit properly, no longer clinging to the sides of their mounts for dear life.
+ p.small.muted by Kiwibot, Lemoness, and SabreCat
h5 6/1/2015 - JUNE MYSTERY ITEM!
tr
td
diff --git a/website/views/shared/tasks/edit/advanced_options.jade b/website/views/shared/tasks/edit/advanced_options.jade
new file mode 100644
index 0000000000..21f96e7cb4
--- /dev/null
+++ b/website/views/shared/tasks/edit/advanced_options.jade
@@ -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')
diff --git a/website/views/shared/tasks/edit/checklist.jade b/website/views/shared/tasks/edit/checklist.jade
new file mode 100644
index 0000000000..2d5b9c347d
--- /dev/null
+++ b/website/views/shared/tasks/edit/checklist.jade
@@ -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'))
diff --git a/website/views/shared/tasks/edit/dailies/calendar.jade b/website/views/shared/tasks/edit/dailies/calendar.jade
new file mode 100644
index 0000000000..0ffb033acb
--- /dev/null
+++ b/website/views/shared/tasks/edit/dailies/calendar.jade
@@ -0,0 +1,3 @@
+fieldset.option-group.calendar(ng-if='::task.type=="daily"', class="option-group")
+ .dailies
+ include ./repeat_options
diff --git a/website/views/shared/tasks/edit/dailies/repeat_options.jade b/website/views/shared/tasks/edit/dailies/repeat_options.jade
new file mode 100644
index 0000000000..e89f7d1d3b
--- /dev/null
+++ b/website/views/shared/tasks/edit/dailies/repeat_options.jade
@@ -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)}}
diff --git a/website/views/shared/tasks/edit/habits/plus_minus.jade b/website/views/shared/tasks/edit/habits/plus_minus.jade
new file mode 100644
index 0000000000..bbe17b7d4e
--- /dev/null
+++ b/website/views/shared/tasks/edit/habits/plus_minus.jade
@@ -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')
diff --git a/website/views/shared/tasks/edit/index.jade b/website/views/shared/tasks/edit/index.jade
new file mode 100644
index 0000000000..ce29ee8896
--- /dev/null
+++ b/website/views/shared/tasks/edit/index.jade
@@ -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')
+ |
+ a(ng-click="removeTask(task, obj[list.type+'s'])")=env.t('removeIt')
+ div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"')
+ p
+ |
+ =env.t('brokenChallenge')
+ p
+ a(ng-click='unlink(task, "keep-all")')=env.t('keepThem')
+ | |
+ 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')
+ | |
+ 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')
+ | |
+ 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')
diff --git a/website/views/shared/tasks/edit/rewards/pricing.jade b/website/views/shared/tasks/edit/rewards/pricing.jade
new file mode 100644
index 0000000000..f322dc06d9
--- /dev/null
+++ b/website/views/shared/tasks/edit/rewards/pricing.jade
@@ -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
diff --git a/website/views/shared/tasks/edit/tags.jade b/website/views/shared/tasks/edit/tags.jade
new file mode 100644
index 0000000000..ab512731fd
--- /dev/null
+++ b/website/views/shared/tasks/edit/tags.jade
@@ -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')
diff --git a/website/views/shared/tasks/edit/text_notes.jade b/website/views/shared/tasks/edit/text_notes.jade
new file mode 100644
index 0000000000..72cddbd59c
--- /dev/null
+++ b/website/views/shared/tasks/edit/text_notes.jade
@@ -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}")
diff --git a/website/views/shared/tasks/edit/todos/due_date.jade b/website/views/shared/tasks/edit/todos/due_date.jade
new file mode 100644
index 0000000000..c9cbfe7d0e
--- /dev/null
+++ b/website/views/shared/tasks/edit/todos/due_date.jade
@@ -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')
+
diff --git a/website/views/shared/tasks/index.jade b/website/views/shared/tasks/index.jade
new file mode 100644
index 0000000000..972a62f1db
--- /dev/null
+++ b/website/views/shared/tasks/index.jade
@@ -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
+ =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')
diff --git a/website/views/shared/tasks/lists.jade b/website/views/shared/tasks/lists.jade
deleted file mode 100644
index f746992e9a..0000000000
--- a/website/views/shared/tasks/lists.jade
+++ /dev/null
@@ -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.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:""})
- li!=env.t('habitHelp2', {minusIcon:""})
- li!=env.t('habitHelp3')
- li!=env.t('newbieGuild', {linkStart:"", linkEnd: ""})
- div(ng-switch-when='daily')
- ul
- li!=env.t('dailyHelp1', {emphasisStart:"", emphasisEnd:"", pencilIcon:""})
- li!=env.t('dailyHelp2')
- li!=env.t('dailyHelp3', {emphasisStart:"", emphasisEnd:""})
- li!=env.t('dailyHelp4', {linkStart:"", linkEnd:""})
- li!=env.t('dailyHelp5')
- li!=env.t('newbieGuild', {linkStart:"", linkEnd: ""})
- 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:"", linkEnd: ""})
- div(ng-switch-when='reward')
- ul
- li!=env.t('rewardHelp1', {linkStart:"", linkEnd: ""})
- li!=env.t('rewardHelp2', {linkStart:"", linkEnd: ""})
- li!=env.t('rewardHelp3')
- li!=env.t('rewardHelp4')
- li!=env.t('newbieGuild', {linkStart:"", linkEnd: ""})
-
- // 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
- =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')
diff --git a/website/views/shared/tasks/meta_controls.jade b/website/views/shared/tasks/meta_controls.jade
new file mode 100644
index 0000000000..acff83ef6f
--- /dev/null
+++ b/website/views/shared/tasks/meta_controls.jade
@@ -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
+ |
+ span(ng-show='task.streak') {{task.streak}}
+ span(tooltip=env.t('streakCounter'))
+ span.glyphicon.glyphicon-forward
+ |
+
+ // 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'))
+ |
+ span.glyphicon.glyphicon-pencil(ng-hide='task._editing')
+ |
+ a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip=env.t('cancel'))
+ span.glyphicon.glyphicon-remove(ng-hide='!task._editing')
+ |
+ // save
+ a(ng-hide='!task._editing', ng-click='editTask(task);saveTask(task)', tooltip=env.t('save'))
+ span.glyphicon.glyphicon-ok(ng-hide='!task._editing')
+ |
+ //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')
+ |
+ span(ng-if='!task.challenge.broken')
+ span.glyphicon.glyphicon-bullhorn(tooltip=env.t('challenge'))
+ |
+ // delete
+ a(ng-if='!task.challenge.id', ng-click='removeTask(task, obj[list.type+"s"])', tooltip=env.t('delete'))
+ span.glyphicon.glyphicon-trash
+ |
+
+ // chart
+ a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip=env.t('progress'))
+ span.glyphicon.glyphicon-signal
+ |
+ // 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
+ |
diff --git a/website/views/shared/tasks/task.jade b/website/views/shared/tasks/task.jade
index 80f2769df3..3675e158f5 100644
--- a/website/views/shared/tasks/task.jade
+++ b/website/views/shared/tasks/task.jade
@@ -1,238 +1,17 @@
-li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | filterByTextAndNotes: obj.filterQuery | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.popoverOpen ? '' : task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
- // right-hand side control buttons
- .task-meta-controls
+li(id='task-{{::task.id}}',
+ ng-repeat='task in obj[list.type+"s"] | filterByTextAndNotes: obj.filterQuery | conditionalOrderBy: list.view=="dated":"date"',
+ class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}',
+ ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}',
+ ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)',
+ ng-show='shouldShow(task, list, user.preferences)',
+ popover-trigger='mouseenter', popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}',
+ data-popover-html="{{task.popoverOpen ? '' : task.notes | markdown}}")
- // Due Date
- span(ng-if='task.type=="todo" && task.date')
- span(ng-class='{"label label-danger":(moment(task.date).isBefore(_today, "days") && !task.completed)}') {{task.date | date:(user.preferences.dateFormat.indexOf('yyyy') == 0 ? user.preferences.dateFormat.substr(5) : user.preferences.dateFormat.substr(0,5))}}
+ ng-form(name='taskForm')
+ include ./meta_controls
- // Streak
- |
- span(ng-show='task.streak') {{task.streak}}
- span(tooltip=env.t('streakCounter'))
- span.glyphicon.glyphicon-forward
- |
+ include ./task_view/index
- // Icons only available if you own the tasks (aka, hidden from challenge stats)
- span(ng-if='!obj._locked')
- a(ng-click='pushTask(task,$index,"top")', tooltip=env.t('pushTaskToTop'))
- span.glyphicon.glyphicon-open
- // a(ng-click='pushTask(task,$index,"bottom")', tooltip=env.t('pushTaskToBottom'))
- // span.glyphicon.glyphicon-import
- // // glyphicon-import or glyphicon-save or glyphicon-sort-by-attributes
- a.badge(ng-if='task.checklist[0]', ng-class='{"badge-success":checklistCompletion(task.checklist) == task.checklist.length}', ng-click='collapseChecklist(task)', tooltip=env.t('expandCollapse'))
- |{{checklistCompletion(task.checklist)}}/{{task.checklist.length}}
- span.glyphicon.glyphicon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
- // edit
- a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit'))
- |
- span.glyphicon.glyphicon-pencil(ng-hide='task._editing')
- |
- a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip=env.t('cancel'))
- span.glyphicon.glyphicon-remove(ng-hide='!task._editing')
- |
- // save
- a(ng-hide='!task._editing', ng-click='editTask(task);saveTask(task)', tooltip=env.t('save'))
- span.glyphicon.glyphicon-ok(ng-hide='!task._editing')
- |
- //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')
- |
- span(ng-if='!task.challenge.broken')
- span.glyphicon.glyphicon-bullhorn(tooltip=env.t('challenge'))
- |
- // delete
- a(ng-if='!task.challenge.id', ng-click='removeTask(task, obj[list.type+"s"])', tooltip=env.t('delete'))
- span.glyphicon.glyphicon-trash
- |
-
- // chart
- a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip=env.t('progress'))
- span.glyphicon.glyphicon-signal
- |
- // 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
- |
-
- // 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')
- |
- a(ng-click="removeTask(task, obj[list.type+'s'])")=env.t('removeIt')
- div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"')
- p
- |
- =env.t('brokenChallenge')
- p
- a(ng-click='unlink(task, "keep-all")')=env.t('keepThem')
- | |
- 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')
- | |
- 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')
- | |
- a(ng-click="unlink(task, 'remove-all')")=env.t('removeThem')
-
- // Checklists
- .task-checklist-edit(ng-if='!$state.includes("options.social.challenges")')
- ul
- li
- button(type='button', ng-if='!task.checklist[0] && (task.type=="daily" || task.type=="todo")',ng-click='addChecklist(task)')
- span.glyphicon.glyphicon-tasks
- span=env.t('addChecklist')
- form.checklist-form(ng-if='task.checklist')
- fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
- legend.option-title
- span.hint(popover=env.t('checklistText'),popover-trigger='mouseenter',popover-placement='bottom')=env.t('checklist')
- ul(hrpg-sort-checklist)
- li(ng-repeat='item in task.checklist')
- //input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
- //-,ng-blur='saveTask(task,true)')
- span.checklist-icon.glyphicon.glyphicon-resize-vertical()
- input(type='text',ng-model='item.text', ui-keyup="{'13':'addChecklistItem(task,$event,$index)','38 40':'navigateChecklist(task,$index,$event)'}")
- a(ng-click='removeChecklistItem(task,$event,$index,true)')
- span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))
-
- form(ng-submit='saveTask(task,false,true)')
- // text & notes
- fieldset.option-group
- label.option-title=env.t('text')
- input.option-content(type='text', ng-model='task.text', required, ng-disabled='task.challenge.id')
-
- label.option-title=env.t('extraNotes')
- textarea.option-content(rows='3', ng-model='task.notes', ng-model-options="{debounce: 1000}")
-
- // if Habit, plus/minus command options
- fieldset.option-group.plusminus(ng-if='task.type=="habit" && !task.challenge.id')
- legend.option-title=env.t('direction/Actions')
- span.task-checker
- input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
- label(for='{{obj._id}}_{{task.id}}-option-plus')
- span.task-checker
- input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
- label(for='{{obj._id}}_{{task.id}}-option-minus')
-
- // if Daily, calendar
- fieldset(ng-if='::task.type=="daily"', class="option-group")
- legend.option-title=env.t('repeat')
- ul.repeat-days(bindonce)
- // note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
- li
- button(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)') {{::moment.weekdaysMin(0)}}
- li
- button(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)') {{::moment.weekdaysMin(1)}}
- li
- button(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)') {{::moment.weekdaysMin(2)}}
- li
- button(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)') {{::moment.weekdaysMin(3)}}
- li
- button(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)') {{::moment.weekdaysMin(4)}}
- li
- button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
- li
- button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
-
- // if Reward, pricing
- fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
- legend.option-title=env.t('price')
- input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value')
- .money.input-suffix
- span.shop_gold
-
- // if Todos, the due date
- fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
- legend.option-title=env.t('dueDate')
- input.option-content.datepicker(type='text', datepicker-popup='{{user.preferences.dateFormat}}', ng-model='task.date', is-open='datepickerOpened', ng-click='datepickerOpened = true')
-
- // Tags
- fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
- p.option-title.mega(ng-click='task._tags = !task._tags', tooltip=env.t('expandCollapse'))=env.t('tags')
- label.checkbox(ng-repeat='tag in user.tags', ng-class="{visuallyhidden: task._tags}")
- input(type='checkbox', ng-model='task.tags[tag.id]')
- markdown(text='tag.name')
-
- // Advanced Options
- span(ng-if='::task.type!="reward"')
- p.option-title.mega(ng-click='task._advanced = !task._advanced', tooltip=env.t('expandCollapse'))=env.t('advancedOptions')
- fieldset.option-group.advanced-option(ng-class="{visuallyhidden: task._advanced}")
- legend.option-title
- a.hint.priority-multiplier-help(href='http://habitrpg.wikia.com/wiki/Difficulty', target='_blank', popover-title=env.t('difficultyHelpTitle'), popover-trigger='mouseenter', popover=env.t('difficultyHelpContent'))=env.t('difficulty')
- ul.priority-multiplier
- li
- button(type='button', ng-class='{active: task.priority==1 || !task.priority}', ng-click='task.challenge.id || (task.priority=1)')=env.t('easy')
- li
- button(type='button', ng-class='{active: task.priority==1.5}', ng-click='task.challenge.id || (task.priority=1.5)')=env.t('medium')
- li
- button(type='button', ng-class='{active: task.priority==2}', ng-click='task.challenge.id || (task.priority=2)')=env.t('hard')
- //span(ng-if='task.type=="daily" && !task.challenge.id')
- span(ng-if='task.type=="daily"')
- legend.option-title.pull-left=env.t('restoreStreak')
- input.option-content(type='number', ng-model='task.streak')
-
- div(ng-if='(user.preferences.allocationMode == "taskbased" && user.preferences.automaticAllocation) || $state.is("options.social.challenges")')
- legend.option-title.pull-left=env.t('attributes')
- ul.task-attributes
- li
- button(type='button', ng-class='{active: task.attribute=="str"}', ng-click='task.attribute="str"')=env.t('physical')
- li
- button(type='button', ng-class='{active: task.attribute=="int"}', ng-click='task.attribute="int"')=env.t('mental')
- li
- button(type='button', ng-class='{active: task.attribute=="con"}', ng-click='task.attribute="con"')=env.t('social')
- li
- button(type='button', ng-class='{active: task.attribute=="per"}', ng-click='task.attribute="per"', popover=env.t('otherExamples'), popover-trigger='mouseenter', popover-placement='top')=env.t('other')
-
- .save-close
- button(type='submit')=env.t('saveAndClose')
+ include ./edit/index
div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]')
diff --git a/website/views/shared/tasks/task_view/add_new.jade b/website/views/shared/tasks/task_view/add_new.jade
new file mode 100644
index 0000000000..aee14a33b6
--- /dev/null
+++ b/website/views/shared/tasks/task_view/add_new.jade
@@ -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')
diff --git a/website/views/shared/tasks/task_view/graph.jade b/website/views/shared/tasks/task_view/graph.jade
new file mode 100644
index 0000000000..2ccc3cae1c
--- /dev/null
+++ b/website/views/shared/tasks/task_view/graph.jade
@@ -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.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'})
diff --git a/website/views/shared/tasks/task_view/help.jade b/website/views/shared/tasks/task_view/help.jade
new file mode 100644
index 0000000000..718d82e411
--- /dev/null
+++ b/website/views/shared/tasks/task_view/help.jade
@@ -0,0 +1,25 @@
+div(ng-if='list.help', ng-switch='::list.type')
+ ul(ng-switch-when='habit')
+ li!=env.t('habitHelp1', {plusIcon:""})
+ li!=env.t('habitHelp2', {minusIcon:""})
+ li!=env.t('habitHelp3')
+ li!=env.t('newbieGuild', {linkStart:"", linkEnd: ""})
+ ul(ng-switch-when='daily')
+ li!=env.t('dailyHelp1', {emphasisStart:"", emphasisEnd:"", pencilIcon:""})
+ li=env.t('dailyHelp2')
+ li!=env.t('dailyHelp3', {emphasisStart:"", emphasisEnd:""})
+ li!=env.t('dailyHelp4', {linkStart:"", linkEnd:""})
+ li!=env.t('dailyHelp5')
+ li!=env.t('newbieGuild', {linkStart:"", linkEnd: ""})
+ 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:"", linkEnd: ""})
+ ul(ng-switch-when='reward')
+ li!=env.t('rewardHelp1', {linkStart:"", linkEnd: ""})
+ li!=env.t('rewardHelp2', {linkStart:"", linkEnd: ""})
+ li=env.t('rewardHelp3')
+ li!=env.t('rewardHelp4')
+ li!=env.t('newbieGuild', {linkStart:"", linkEnd: ""})
diff --git a/website/views/shared/tasks/task_view/index.jade b/website/views/shared/tasks/task_view/index.jade
new file mode 100644
index 0000000000..f15490969d
--- /dev/null
+++ b/website/views/shared/tasks/task_view/index.jade
@@ -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')
diff --git a/website/views/shared/tasks/task_view/mixins.jade b/website/views/shared/tasks/task_view/mixins.jade
new file mode 100644
index 0000000000..775f194a76
--- /dev/null
+++ b/website/views/shared/tasks/task_view/mixins.jade
@@ -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()}}
diff --git a/website/views/shared/tasks/task_view/spells.jade b/website/views/shared/tasks/task_view/spells.jade
new file mode 100644
index 0000000000..9360b0e656
--- /dev/null
+++ b/website/views/shared/tasks/task_view/spells.jade
@@ -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()}}
diff --git a/website/views/shared/tasks/task_view/static_rewards.jade b/website/views/shared/tasks/task_view/static_rewards.jade
new file mode 100644
index 0000000000..7a19f66db3
--- /dev/null
+++ b/website/views/shared/tasks/task_view/static_rewards.jade
@@ -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()}}