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()}}