Join 200,000 players making it fun to achieve goals!
-
-
-
-
Featured in
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
HabitRPG is a free habit building and productivity app that treats your real life like a game. With in-game rewards and punishments to motivate you and a strong social network to inspire you, HabitRPG can help you achieve your goals to become healthy, hard-working, and happy.
-
-
-
-
-
-
-
What people say...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
I can't tell you how many time and task tracking systems I've tried over the decades... HRPG is the only thing I've used that actually helps me get things done rather than just list them.
- Drag0nsilver
-
-
-
-
-
-
-
-
-
-
HabitRPG is the reason I got a killer, high-paying job... and even more miraculous, I'm now a daily flosser!
- frabjabulous
-
-
-
-
-
-
-
-
-
-
Awesome product, just started a few days ago and already more conscious and productive with my time!
- AndeeLiao
-
-
-
-
-
-
-
-
-
Couldn't NOT talk about HabitRPG during my speech in Madrid. Must-have tool for freelancers who still need a boss.
Eggs and items drop when you complete your tasks. Be as productive as possible to collect pets and mounts!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Achievement Badges
-
Do something totally awesome? Get a badge and show it off!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Equipment and extras
-
Buy limited edition equipment, potions, and other virtual goodies in our Market with your task rewards!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Social play
-
Join common-interest groups with like-minded people.
Create Challenges to compete against other users.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/website/public/front/more-info.html b/website/public/front/more-info.html
deleted file mode 100644
index 5189db93c1..0000000000
--- a/website/public/front/more-info.html
+++ /dev/null
@@ -1,28 +0,0 @@
-HabitRPG | Gamify Your Life
-
-
-
-
-
-
-
-
-
-
-
-
-
-
A free habit building app that treats your life like a game.
The problem with most productivity apps on the market is that they provide no incentive to continue using them. HabitRPG fixes this by making habit building fun! By rewarding you for your successes and penalizing you for slip-ups, HabitRPG provides external motivation for completing your day-to-day activities.
Instant Gratification
Whenever you reinforce a positive habit, complete a daily task, or take care of an old to-do, HabitRPG immediately rewards you with experience points and gold. As you gain experience, you can level up, increasing your stats and unlocking more features, like classes and pets. Gold can be spent on in-game items that change your experience or personalized rewards you've created for motivation. When even the smallest successes provide you with an immediate reward, you're less likely to procrastinate.
Consequences
Whenever you indulge in a bad habit or fail to complete one of your daily tasks, you lose health. If your health drops too low, you die and lose some of the progress you've made. By providing immediate consequences, HabitRPG can help break bad habits and procrastination cycles before they cause real-world problems.
Accountability
With an active community, HabitRPG provides the accountability you need to stay on task. With the party system, you can bring in a group of your closest friends to cheer you on. The guild system allows you to find people with similar interests or obstacles, so you can share your goals and swap tips on how to tackle your problems. On HabitRPG, the community means that you have both the support and the accountability you need to succeed.
\ No newline at end of file
diff --git a/website/public/front/statichome.html b/website/public/front/statichome.html
deleted file mode 100644
index 3bf04f348f..0000000000
--- a/website/public/front/statichome.html
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/website/public/js/controllers/challengesCtrl.js b/website/public/js/controllers/challengesCtrl.js
index 24985e85ac..770d736803 100644
--- a/website/public/js/controllers/challengesCtrl.js
+++ b/website/public/js/controllers/challengesCtrl.js
@@ -19,7 +19,9 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
$scope.challenges = challenges;
$scope.groupsFilter = _.uniq(_.pluck(challenges, 'group'), function(g){return g._id});
$scope.search = {
- group: _.transform($scope.groups, function(m,g){m[g._id]=true;})
+ group: _.transform($scope.groups, function(m,g){m[g._id]=true;}),
+ _isMember: "either",
+ _isOwner: "either"
};
});
}
@@ -254,18 +256,10 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
// Filtering
//------------------------------------------------------------
-// $scope.$watch('search', function(search){
-// if (!search) $scope.filteredChallenges = $scope.challenges;
-// $scope.filteredChallenges = $filter('filter')($scope.challenges, function(chal) {
-// return (search.group[chal.group._id] &&
-// (typeof search._isMember == 'undefined' || search._isMember == chal._isMember));
-// })
-// })
- // TODO probably better to use $watch above, to avoid this being calculated on every digest cycle
$scope.filterChallenges = function(chal){
- return (!$scope.search) ? true :
- ($scope.search.group[chal.group._id] &&
- (typeof $scope.search._isMember == 'undefined' || $scope.search._isMember == chal._isMember));
+ if (!$scope.search) return true;
+
+ return _shouldShowChallenge(chal);
}
$scope.$watch('newChallenge.group', function(gid){
@@ -286,4 +280,16 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
$scope.shouldShow = function(task, list, prefs){
return true;
};
+
+ function _shouldShowChallenge(chal) {
+ // Have to check that the leader object exists first in the
+ // case where a challenge's leader deletes their account
+ var userIsOwner = (chal.leader && chal.leader._id) == User.user.id;
+
+ var groupSelected = $scope.search.group[chal.group._id];
+ var checkOwner = $scope.search._isOwner === 'either' || (userIsOwner === $scope.search._isOwner);
+ var checkMember = $scope.search._isMember === 'either' || (chal._isMember === $scope.search._isMember);
+
+ return groupSelected && checkOwner && checkMember;
+ }
}]);
diff --git a/website/public/js/controllers/filtersCtrl.js b/website/public/js/controllers/filtersCtrl.js
index b1204b86d3..cfdc45658d 100644
--- a/website/public/js/controllers/filtersCtrl.js
+++ b/website/public/js/controllers/filtersCtrl.js
@@ -5,6 +5,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared',
var user = User.user;
$scope._editing = false;
$scope._newTag = {name:''};
+ $scope.filterQuery = '';
var tagsSnap; // used to compare which tags need updating
@@ -30,6 +31,11 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared',
// User.save();
};
+ $scope.updateTaskFilter = function(){
+ user.filterQuery = $scope.filterQuery;
+ };
+ $scope.updateTaskFilter();
+
$scope.createTag = function() {
User.user.ops.addTag({body:{name:$scope._newTag.name, id:Shared.uuid()}});
$scope._newTag.name = '';
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/public/js/filters/filters.js b/website/public/js/filters/filters.js
deleted file mode 100644
index 4578c1a5db..0000000000
--- a/website/public/js/filters/filters.js
+++ /dev/null
@@ -1,31 +0,0 @@
-angular.module('habitrpg')
- .filter('gold', function () {
- return function (gp) {
- return Math.floor(gp);
- }
- })
- .filter('silver', function () {
- return function (gp) {
- return Math.floor((gp - Math.floor(gp))*100);
- }
- })
- .filter('htmlDecode',function(){
- return function(html){
- return $('').html(html).text();
- }
- })
- .filter('goldRoundThousandsToK', function(){
- return function (gp) {
- return (gp > 999999999) ? (gp / Math.pow(10, 9)).toFixed(1) + "b" :
- (gp > 999999) ? (gp / Math.pow(10, 6)).toFixed(1) + "m" :
- (gp > 999) ? (gp / Math.pow(10, 3)).toFixed(1) + "k" : gp;
- }
- })
- .filter('conditionalOrderBy', ['$filter', function($filter) {
- return function (array, predicate, sortPredicate, reverseOrder) {
- if (predicate) {
- return $filter('orderBy')(array, sortPredicate, reverseOrder);
- }
- return array;
- };
- }]);
diff --git a/website/public/js/filters/money.js b/website/public/js/filters/money.js
new file mode 100644
index 0000000000..ae22604d49
--- /dev/null
+++ b/website/public/js/filters/money.js
@@ -0,0 +1,11 @@
+angular.module('habitrpg')
+ .filter('gold', function () {
+ return function (gp) {
+ return Math.floor(gp);
+ }
+ })
+ .filter('silver', function () {
+ return function (gp) {
+ return Math.floor((gp - Math.floor(gp))*100);
+ }
+ });
diff --git a/website/public/js/filters/roundLargeNumbers.js b/website/public/js/filters/roundLargeNumbers.js
new file mode 100644
index 0000000000..9eebd6c546
--- /dev/null
+++ b/website/public/js/filters/roundLargeNumbers.js
@@ -0,0 +1,30 @@
+angular.module('habitrpg')
+ .filter('roundLargeNumbers', function(){
+ return function (num) {
+ return _calculateRoundedNumber(num);
+ }
+ });
+
+function _calculateRoundedNumber(num) {
+ if (num > 999999999) {
+ return _convertToBillion(num);
+ } else if (num > 999999) {
+ return _convertToMillion(num);
+ } else if (num > 999) {
+ return _convertToThousand(num);
+ } else {
+ return num;
+ }
+}
+
+function _convertToThousand(num) {
+ return (num / Math.pow(10, 3)).toFixed(1) + "k";
+}
+
+function _convertToMillion(num) {
+ return (num / Math.pow(10, 6)).toFixed(1) + "m";
+}
+
+function _convertToBillion(num) {
+ return (num / Math.pow(10, 9)).toFixed(1) + "b";
+}
diff --git a/website/public/js/filters/taskOrdering.js b/website/public/js/filters/taskOrdering.js
new file mode 100644
index 0000000000..42abd471c0
--- /dev/null
+++ b/website/public/js/filters/taskOrdering.js
@@ -0,0 +1,30 @@
+angular.module('habitrpg')
+ .filter('conditionalOrderBy', ['$filter', function($filter) {
+ return function (array, predicate, sortPredicate, reverseOrder) {
+ if (predicate) {
+ return $filter('orderBy')(array, sortPredicate, reverseOrder);
+ }
+ return array;
+ };
+ }])
+ .filter('filterByTextAndNotes', ['$filter', function($filter) {
+ return function (input, term) {
+ if (!input) return;
+
+ if (!angular.isString(term) || term.legth === 0) {
+ return input;
+ }
+
+ term = new RegExp(term, 'i');
+
+ var result = [];
+
+ for (var i = 0; i < input.length; i++) {
+ if (term.test(input[i].text) || term.test(input[i].notes)) {
+ result.push(input[i]);
+ }
+ }
+
+ return result;
+ };
+ }]);
diff --git a/website/public/manifest.json b/website/public/manifest.json
index 7436040c57..d7afa0e961 100644
--- a/website/public/manifest.json
+++ b/website/public/manifest.json
@@ -38,6 +38,7 @@
"js/app.js",
"common/script/public/config.js",
+
"js/services/sharedServices.js",
"js/services/notificationServices.js",
"common/script/public/userServices.js",
@@ -48,7 +49,9 @@
"js/services/challengeServices.js",
"js/services/paymentServices.js",
- "js/filters/filters.js",
+ "js/filters/money.js",
+ "js/filters/roundLargeNumbers.js",
+ "js/filters/taskOrdering.js",
"js/directives/focus-me.directive.js",
"js/directives/from-now.directive.js",
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/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/main/filters.jade b/website/views/main/filters.jade
index 20e244cce2..b72d424364 100644
--- a/website/views/main/filters.jade
+++ b/website/views/main/filters.jade
@@ -1,6 +1,10 @@
.container-fluid
.row
.filters(ng-controller='FiltersCtrl')
+ .input-group.input-group-sm.filters-search.pull-right
+ input.form-control(type='text', placeholder=env.t('search'), ng-model='filterQuery', ng-change='updateTaskFilter()', ng-model-options='{ debounce: 250 }')
+ .input-group-addon
+ .glyphicon.glyphicon-search
ul.filters-controls
li=env.t('tags')
//- Edit button
diff --git a/website/views/options/social/boss.jade b/website/views/options/social/boss.jade
index 1574f648e8..6411462bbe 100644
--- a/website/views/options/social/boss.jade
+++ b/website/views/options/social/boss.jade
@@ -52,13 +52,13 @@ mixin boss(tavern, mobile)
.meter.health
.bar(style='width: {{Shared.percent(progress.hp, boss.hp)}}%;')
span.meter-text.value
- | {{Math.ceil(progress.hp) | goldRoundThousandsToK}} / {{boss.hp | goldRoundThousandsToK}}
+ | {{Math.ceil(progress.hp) | roundLargeNumbers}} / {{boss.hp | roundLargeNumbers}}
.meter-label(tooltip='Rage', ng-if='boss.rage')
span.glyphicon.glyphicon-fire
.meter.mana(ng-if='boss.rage',popover="{{::boss.rage.description()}}",popover-title="{{::boss.rage.title()}}",popover-trigger='mouseenter',popover-placement='right')
.bar(style='width: {{Shared.percent(progress.rage, boss.rage.value)}}%;')
span.meter-text.value
- | {{Math.ceil(progress.rage) | goldRoundThousandsToK}} / {{boss.rage.value | goldRoundThousandsToK}}
+ | {{Math.ceil(progress.rage) | roundLargeNumbers}} / {{boss.rage.value | roundLargeNumbers}}
div(ng-if='::Content.quests[group.quest.key].collect')
div(class="quest_{{::group.quest.key}}")
h4=env.t('collected') + ':'
diff --git a/website/views/options/social/challenges.jade b/website/views/options/social/challenges.jade
index b2e0d2c1a4..4bfa55c791 100644
--- a/website/views/options/social/challenges.jade
+++ b/website/views/options/social/challenges.jade
@@ -81,16 +81,29 @@ script(type='text/ng-template', id='partials/options.social.challenges.html')
h4=env.t('membership')
.radio
label
- input(type='radio', name='search-participation-radio', ng-click='search._isMember = true')
+ input(type='radio', name='search-participation-radio', ng-model='search._isMember', ng-value='true', ng-change='filterChallenges')
=env.t('participating')
.radio
label
- input(type='radio', name='search-participation-radio', ng-click='search._isMember = false')
+ input(type='radio', name='search-participation-radio', ng-model='search._isMember', ng-value='false', ng-change='filterChallenges')
=env.t('notParticipating')
.radio
label
- input(type='radio', name='search-participation-radio', ng-click='search._isMember = undefined', checked='checked')
+ input(type='radio', name='search-participation-radio', ng-model='search._isMember', value='either', ng-change='filterChallenges')
=env.t('either')
+ h4=env.t('challengedOwnedFilterHeader')
+ .radio
+ label
+ input(type='radio', name='search-owner-radio', ng-model='search._isOwner', ng-value='true', ng-change='filterChallenges')
+ =env.t('challengedOwnedFilter')
+ .radio
+ label
+ input(type='radio', name='search-owner-radio', ng-model='search._isOwner', ng-value='false', ng-change='filterChallenges')
+ =env.t('challengedNotOwnedFilter')
+ .radio
+ label
+ input(type='radio', name='search-owner-radio', ng-model='search._isOwner', value='either', ng-change='filterChallenges', checked='checked')
+ =env.t('challengedEitherOwnedFilter')
.col-md-10
a.btn.btn-info#back-to-challenges(ng-show="cid", ui-sref='options.social.challenges', ui-sref-opts='{reload: true}')
| Back to all challenges
diff --git a/website/views/shared/header/menu.jade b/website/views/shared/header/menu.jade
index ebfdd6a6ff..f0dd838f81 100644
--- a/website/views/shared/header/menu.jade
+++ b/website/views/shared/header/menu.jade
@@ -146,8 +146,9 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')=env.t('dataTool')
li
a(ui-sref='options.settings.export')=env.t('exportData')
- li.toolbar-button-dropdown
+ li.toolbar-button-dropdown.highlight
a(target="_blank" href='http://habitrpg.wikia.com/wiki/')
+ span.glyphicon.glyphicon-question-sign
span=env.t('help')
a(ng-click='expandMenu("help")', ng-class='{active: _expandedMenu == "help"}')
span ☰
@@ -169,7 +170,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
a(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour')
ul.toolbar-subscribe(ng-if='!user.purchased.plan.customerId')
li.toolbar-subscribe-button
- button(ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover-title=env.t('subscriptions'),popover=env.t('subDescription'),popover-append-to-body='true')=env.t('subscribe')
+ button.highlight(ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover-title=env.t('subscriptions'),popover=env.t('subDescription'),popover-append-to-body='true')=env.t('subscribe')
ul.toolbar-options
li.toolbar-notifs
a(ng-click='expandMenu("notifs")')
@@ -264,7 +265,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
span.gem-text {{user.balance * 4 | number:0}}
li.toolbar-currency.gold(popover=env.t('gold') + ' ({{Shared.gold(user.stats.gp)}})', popover-placement='bottom',popover-trigger='mouseenter')
span.shop_gold
- span {{Shared.gold(user.stats.gp) | goldRoundThousandsToK}}
+ span {{Shared.gold(user.stats.gp) | roundLargeNumbers}}
li.toolbar-currency.silver(popover=env.t('silver'), popover-placement='bottom',popover-trigger='mouseenter')
span.shop_silver
span {{Shared.silver(user.stats.gp)}}
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..a34051ddf5
--- /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', ng-disabled='task.challenge.id')
+
+ hr
+
+ .form-group
+ legend.option-title=env.t('repeat')
+ select.form-control(ng-model='task.frequency', ng-disabled='task.challenge.id')
+ 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..ed77bb0a76
--- /dev/null
+++ b/website/views/shared/tasks/edit/checklist.jade
@@ -0,0 +1,23 @@
+.task-checklist-edit(ng-if='::!$state.includes("options.social.challenges") && (task.type=="daily" || task.type=="todo")')
+ ul
+ li
+ button(type='button', ng-if='!task.checklist[0]'
+ popover=env.t('checklistText'), popover-trigger='mouseenter', popover-placement='bottom',
+ ng-click='addChecklist(task)')
+ span.glyphicon.glyphicon-tasks
+ span=env.t('addChecklist')
+
+ .checklist-form(ng-if='task.checklist')
+ fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
+ legend.option-title(ng-if='task.checklist[0]')
+ 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..b27755bd03
--- /dev/null
+++ b/website/views/shared/tasks/edit/dailies/repeat_options.jade
@@ -0,0 +1,25 @@
+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', ng-disabled='task.challenge.id', 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
+ mixin dayOfWeek(day, num)
+ li
+ button(type='button', ng-class='{active: task.repeat.#{day}}',
+ ng-disabled='task.challenge.id', ng-click='task.repeat.#{day} = !task.repeat.#{day}')
+ | {{::moment.weekdaysMin(#{num})}}
+
+ +dayOfWeek('su', 0)
+ +dayOfWeek('m', 1)
+ +dayOfWeek('t', 2)
+ +dayOfWeek('w', 3)
+ +dayOfWeek('th', 4)
+ +dayOfWeek('f', 5)
+ +dayOfWeek('s', 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..95dc1ef9af
--- /dev/null
+++ b/website/views/shared/tasks/edit/text_notes.jade
@@ -0,0 +1,7 @@
+fieldset.option-group
+ label.option-title=env.t('text')
+ input.form-control(type='text', ng-model='task.text', required, ng-disabled='task.challenge.id')
+
+fieldset.option-group
+ label.option-title=env.t('extraNotes')
+ textarea.form-control(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 013f653bb8..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 9fa9a68bbe..3675e158f5 100644
--- a/website/views/shared/tasks/task.jade
+++ b/website/views/shared/tasks/task.jade
@@ -1,241 +1,17 @@
-li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | 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')
-
-
- form(ng-submit='saveTask(task,false,true)')
- // Title text input
- label.option-title=env.t('title')
- input.option-content(type='text', ng-model='task.text', required, ng-disabled='task.challenge.id')
-
- // 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)')
- // Notes text input
- fieldset.option-group
- 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..dfc3359ff5
--- /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=env.t('clickForHelp'))
+ 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()}}