Commit e91de4c040a46671fd01296eb3d65aa0ec73b4b7

Authored by Laura Hawkins

Merge branch 'master' of https://git.ucsd.edu/110swag/flashy-frontend

Showing 7 changed files Side-by-side Diff

scripts/CardGridController.js View file @ e91de4c
1 1 angular.module('flashy.CardGridController', ['ui.router', 'ngAnimate', 'ngWebSocket', 'flashy.DeckFactory']).CardGridController =
2 2 function($scope, $rootScope, $state, $http, $window, $timeout, $stateParams, $websocket, $interval, UserService, Flashcard, Deck) {
  3 + sectionId = parseInt($stateParams.sectionId);
3 4 $scope.cards = []; // all cards
4 5 $scope.cardCols = []; // organized data
5 6 $scope.cardColsShow = []; // displayed data
6 7 $scope.numCols = 0;
7   - $scope.cardTable = {}; // look up table of cards: {'colNum':col, 'obj':card}
8   - $scope.sectionId = parseInt($stateParams.sectionId);
9   - $scope.section = $rootScope.SectionResource.get({sectionId: $scope.sectionId});
10   - $scope.deck = new Deck($scope.sectionId, {
  8 + $scope.section = $rootScope.SectionResource.get({sectionId: sectionId});
  9 + $scope.deck = new Deck(sectionId, {
11 10 cardHideCallback: function(card) {
12 11 $scope.hideCardFromGrid(card);
13 12 }
... ... @@ -93,6 +92,7 @@
93 92  
94 93 $scope.$on('$destroy', function() {
95 94 $scope.deck.cleanup();
  95 + Flashcard.cleanup();
96 96 $rootScope.currentSection = {};
97 97 $(document).off('keydown');
98 98 });
scripts/CardListController.js View file @ e91de4c
1   -angular.module('flashy.CardListController', ['ui.router', 'angular.filter', 'ngSanitize']).
2   - controller('CardListController', function($scope, $rootScope, $state, $http, $stateParams, Flashcard) {
  1 +angular.module('flashy.CardListController', ['ui.router', 'angular.filter', 'ngSanitize', 'flashy.DeckFactory']).
  2 + controller('CardListController', function($scope, $rootScope, $state, $http, $stateParams, Flashcard, Deck) {
3 3 // cards array
4   - sectionId = $stateParams.sectionId;
  4 + sectionId = parseInt($stateParams.sectionId);
  5 + $scope.deck = new Deck(sectionId, {
  6 + cardPullCallback: function(card) {
  7 + Materialize.toast('Pulled!', 3000);
  8 + },
  9 + cardUnpullCallback: function(card) {
  10 + Materialize.toast('Unpulled!', 3000);
  11 + },
  12 + cardHideCallback: function(card) {
  13 + card.is_hidden = true;
  14 + Materialize.toast('Hidden!', 3000);
  15 + },
  16 + cardUnhideCallback: function(card) {
  17 + card.is_hidden = false;
  18 + Materialize.toast('Unhidden!', 3000);
  19 + }
  20 + });
5 21 $rootScope.currentSection = $rootScope.SectionResource.get({sectionId: sectionId});
6 22 $scope.cards = [];
7 23  
8 24 $http.get('/api/sections/' + sectionId + '/flashcards/?hidden=yes').
9 25 success(function(data) {
10   - for (i in data) $scope.cards[data[i].id] = new Flashcard(data[i], $scope.cards);
  26 + for (i in data) $scope.cards[data[i].id] = new Flashcard(data[i], $scope.deck);
11 27 }).
12 28 error(function(err) {
13 29 console.log('pulling feed failed');
14 30 });
15 31  
16   - $scope.viewFeed = function() {
17   - $state.go('feed', {sectionId: sectionId});
18   - console.log('go to feed');
19   - };
20   -
21   -
22   - // unhide card
23   - $scope.unhide = function(card) {
24   - $http.post('/api/flashcards/' + card.id + '/unhide/').
25   - success(function(data) {
26   - console.log(card.text + ' unhidden');
27   -
28   - // locally change hidden
29   - card.is_hidden = false;
30   - Materialize.toast('Unhidden', 3000, 'rounded');
31   - }).
32   - error(function(err) {
33   - console.log('no unhide for you');
34   - });
35   - };
36   -
37   - // hide card
38   - $scope.hide = function(card) {
39   - $http.post('/api/flashcards/' + card.id + '/hide/').
40   - success(function(data) {
41   - console.log(card.text + ' hidden');
42   -
43   - // locally change hidden
44   - card.is_hidden = true;
45   - Materialize.toast('Hidden', 3000, 'rounded');
46   - }).
47   - error(function(err) {
48   - console.log('no hide for you');
49   - });
50   - };
51   -
52   - // pull card
53   - $scope.pull = function(card) {
54   - $http.post('/api/flashcards/' + card.id + '/pull/').
55   - success(function(data) {
56   - console.log(card.text + ' pulled');
57   -
58   - // locally change boolean for display purposes
59   - card.is_in_deck = true;
60   - Materialize.toast('Added to Your Deck', 3000, 'rounded');
61   - }).
62   - error(function(err) {
63   - console.log('no pull for you');
64   - });
65   - };
66   -
67   - // unpull card
68   - $scope.unpull = function(card) {
69   - $http.post('/api/flashcards/' + card.id + '/unpull/').
70   - success(function(data) {
71   - console.log(card.text + ' unpulled');
72   -
73   - // local change for display purposes
74   - card.is_in_deck = false;
75   - Materialize.toast('Removed from Your Deck', 3000, 'rounded');
76   - }).
77   - error(function(err) {
78   - console.log('no unpull for you');
79   - });
80   - };
81   -
82 32 // flag/report card
83 33 $scope.flag = function(card) {
84 34 $http.post('/api/flashcards/' + card.id + '/report/').
85 35 success(function(data) {
86 36 console.log(card.text + ' reported');
87   -
88   - // local change for display purposes
89   - Materialize.toast('Card Flagged', 3000, 'rounded');
90 37 }).
91 38 error(function(err) {
92 39 console.log('no flag for you');
... ... @@ -161,6 +108,10 @@
161 108 (week == 9 && $scope.filter['week9']) ||
162 109 (week == 10 && $scope.filter['week10']);
163 110 };
  111 + $scope.$on('$destroy', function() {
  112 + $scope.deck.cleanup();
  113 + Flashcard.cleanup();
  114 + });
164 115  
165 116 }
166 117 ).
scripts/DeckFactory.js View file @ e91de4c
... ... @@ -30,6 +30,9 @@
30 30 if (data.event_type == 'card_hidden') {
31 31 if (callbacks.cardHideCallback) callbacks.cardHideCallback(card);
32 32 }
  33 + if (data.event_type == 'card_unhidden') {
  34 + if (callbacks.cardUnhideCallback) callbacks.cardUnhideCallback(card);
  35 + }
33 36 });
34 37 this.deckPromise = $http.get('/api/sections/' + sectionId + '/deck/').success(function (data) {
35 38 for (i in data) obj.cards[data[i].id] = new Flashcard(data[i], obj);
scripts/FeedController.js View file @ e91de4c
... ... @@ -26,7 +26,7 @@
26 26 $scope.updateColRanks($scope.cardCols[card.colNum]);
27 27 };
28 28  
29   - $scope.feed_ws = $websocket($scope.ws_host + '/ws/feed/' + $scope.sectionId + '?subscribe-broadcast');
  29 + $scope.feed_ws = $websocket($scope.ws_host + '/ws/feed/' + sectionId + '?subscribe-broadcast');
30 30 $scope.feed_ws.onMessage(function(e) {
31 31 data = JSON.parse(e.data);
32 32 console.log('message', data);
... ... @@ -166,7 +166,7 @@
166 166 $scope.$on('$destroy', function() {
167 167 $scope.feed_ws.close();
168 168 });
169   - return $http.get('/api/sections/' + $scope.sectionId + '/feed/').
  169 + return $http.get('/api/sections/' + sectionId + '/feed/').
170 170 success(function(data) {
171 171 console.log(data);
172 172 $scope.cards = data.map(function(card) {
scripts/FlashcardFactory.js View file @ e91de4c
1 1 angular.module('flashy.FlashcardFactory', ['ui.router']).
2 2 factory('Flashcard', function ($http) {
3 3 var FlashcardCache = [];
  4 + var Deck = null;
4 5 var Flashcard = function (data, deck) {
5 6 if (typeof data == 'number') return FlashcardCache[data];
6 7 if (FlashcardCache[data.id]) return FlashcardCache[data.id];
7   - if (deck) this.deck = deck;
  8 + if (!Deck && deck) Deck = deck;
8 9 for (var k in data) this[k] = data[k];
9 10 this.textPieces = [];
10 11 this.mask.sort(function (a, b) {
... ... @@ -26,7 +27,7 @@
26 27 };
27 28  
28 29 Flashcard.prototype.isInDeck = function () {
29   - return !(typeof this.deck.contains(this.id) === 'undefined');
  30 + return !(typeof Deck.contains(this.id) === 'undefined');
30 31 };
31 32 Flashcard.prototype.pullUnpull = function () {
32 33 if (this.isInDeck()) this.unpull();
... ... @@ -42,6 +43,13 @@
42 43 };
43 44 Flashcard.prototype.hide = function () {
44 45 return $http.post('/api/flashcards/' + this.id + '/hide/');
  46 + };
  47 + Flashcard.prototype.unhide = function () {
  48 + return $http.post('/api/flashcards/' + this.id + '/unhide/');
  49 + };
  50 + Flashcard.cleanup = function () {
  51 + Deck = null;
  52 + FlashcardCache = [];
45 53 };
46 54  
47 55 return Flashcard;
scripts/SettingsController.js View file @ e91de4c
... ... @@ -35,6 +35,7 @@
35 35  
36 36 console.log('executing things outside of module');
37 37 var PUSH_SERVER_URL = '/api/subscribe/';
  38 + var UNPUSH_SERVER_URL = '/api/unsubscribe/';
38 39  
39 40 function onPushSubscription(pushSubscription) {
40 41 console.log('pushSubscription = ', pushSubscription.endpoint);
... ... @@ -50,6 +51,16 @@
50 51 $http.post(PUSH_SERVER_URL, {'registration_id': subscriptionId});
51 52 }
52 53  
  54 + function removeSubscription(pushSubscription) {
  55 + console.log('removing subscription');
  56 + console.log('pushSubscription endpoint = ', pushSubscription.endpoint);
  57 +
  58 + var subscriptionId = pushSubscription.subscriptionId;
  59 +
  60 + console.log('registration_id: ', subscriptionId);
  61 + $http.post(UNPUSH_SERVER_URL, {'registration_id': subscriptionId});
  62 + }
  63 +
53 64 function subscribeDevice() {
54 65 // We need the service worker registration to access the push manager
55 66 navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
56 67  
... ... @@ -64,12 +75,15 @@
64 75 console.log('subscribe() Error: Push permission status = ',
65 76 permissionStatus);
66 77 if (permissionStatus.status === 'denied') {
  78 + pushSwitch.checked = false;
  79 + pushSwitch.disabled = true;
67 80 // The user blocked the permission prompt
68 81 console.log('Ooops Notifications are Blocked',
69 82 'Unfortunately you just permanently blocked notifications. ' +
70 83 'Please unblock / allow them to switch on push ' +
71 84 'notifications.');
72 85 } else {
  86 + pushSwitch.checked = false;
73 87 console.log('Ooops Push Couldn\'t Register',
74 88 '<p>When we tried to ' +
75 89 'get the subscription ID for GCM, something went wrong,' +
... ... @@ -81,6 +95,7 @@
81 95 '</p>');
82 96 }
83 97 }).catch(function(err) {
  98 + pushSwitch.checked = false;
84 99 console.log('Ooops Push Couldn\'t Register',
85 100 '<p>When we tried to ' +
86 101 'get the subscription ID for GCM, something went wrong, not ' +
87 102  
... ... @@ -94,10 +109,13 @@
94 109 } else {
95 110 // Use notification permission to do something
96 111 if (Notification.permission === 'denied') {
  112 + pushSwitch.disabled = true;
  113 + pushSwitch.checked = false;
97 114 console.log('Ooops Notifications are Blocked',
98 115 'Unfortunately you just permanently blocked notifications. ' +
99 116 'Please unblock / allow them to switch on push notifications.');
100 117 } else {
  118 + pushSwitch.checked = false;
101 119 console.log('Ooops Push Couldn\'t Register',
102 120 '<p>When we tried to ' +
103 121 'get the subscription ID for GCM, something went wrong, not ' +
104 122  
105 123  
106 124  
... ... @@ -119,21 +137,27 @@
119 137 function(pushSubscription) {
120 138 // Check we have everything we need to unsubscribe
121 139 if (!pushSubscription) {
  140 + pushSwitch.checked = false;
122 141 return;
123 142 }
124 143  
125 144 // TODO: Remove the device details from the server
126 145 // i.e. the pushSubscription.subscriptionId and
127 146 // pushSubscription.endpoint
  147 + var subscriptionId = pushSubscription.subscriptionId;
128 148  
129 149 pushSubscription.unsubscribe().then(function(successful) {
130 150 console.log('Unsubscribed from push: ', successful);
  151 +
131 152 if (!successful) {
132 153 // The unsubscribe was unsuccessful, but we can
133 154 // remove the subscriptionId from our server
134 155 // and notifications will stop
135 156 // This just may be in a bad state when the user returns
136   - console.error('We were unable to unregister from push');
  157 + pushSwitch.checked = true;
  158 + removeSubscription(pushSubscription);
  159 + console.error('We were unable to unregister from push, but we removed'+
  160 + 'registration id from the server');
137 161 }
138 162  
139 163 }).catch(function(e) {
... ... @@ -151,6 +175,7 @@
151 175 // If the notification permission is denied, it's a permanent block
152 176 switch (permissionStatus.status) {
153 177 case 'denied':
  178 + pushSwitch.disabled = true;
154 179 console.log('Ooops Push has been Blocked',
155 180 'Unfortunately the user permanently blocked push. Please unblock / ' +
156 181 'allow them to switch on push notifications.');
... ... @@ -160,6 +185,7 @@
160 185 console.log('case granted');
161 186 break;
162 187 case 'prompt':
  188 + pushSwitch.checked = false;
163 189 console.log('case prompt');
164 190 break;
165 191 }
... ... @@ -186,6 +212,7 @@
186 212 return;
187 213 }
188 214  
  215 + console.log('update current state.');
189 216 // Update the current state with the
190 217 // subscriptionid and endpoint
191 218 onPushSubscription(subscription);
... ... @@ -202,13 +229,9 @@
202 229 }
203 230  
204 231 function setUpNotificationPermission() {
205   - // If the notification permission is denied, it's a permanent block
206   - if (Notification.permission === 'denied') {
207   - console.log('Ooops Notifications are Blocked',
208   - 'Unfortunately notifications are permanently blocked. Please unblock / ' +
209   - 'allow them to switch on push notifications.');
210   - return;
211   - } else if (Notification.permission === 'default') {
  232 + console.log('setting notification setting');
  233 +
  234 + if (Notification.permission === 'default') {
212 235 console.log('notification permissions === default');
213 236 return;
214 237 }
... ... @@ -237,6 +260,27 @@
237 260  
238 261 // Once the service worker is registered set the initial state
239 262 function initialiseState() {
  263 + // Check if notifications are supported
  264 + if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
  265 + console.warn('Notifications aren\'t supported.');
  266 + return;
  267 + }
  268 + // Check the current Notification permission.
  269 + // If its denied, it's a permanent block until the
  270 + // user changes the permission
  271 + else if (Notification.permission === 'denied') {
  272 + console.log('Ooops Notifications are Blocked',
  273 + 'Unfortunately notifications are permanently blocked. Please unblock / ' +
  274 + 'allow them to switch on push notifications.');
  275 + return;
  276 + }
  277 + // Check if push messaging is supported
  278 + else if (!('PushManager' in window)) {
  279 + console.warn('Push messaging isn\'t supported.');
  280 + return;
  281 + }
  282 +
  283 + pushSwitch.disabled = false;
240 284 // Is the Permissions API supported
241 285 if ('permissions' in navigator) {
242 286 console.log('setting push permissions');
... ... @@ -249,6 +293,9 @@
249 293 }
250 294  
251 295 var enablePushSwitch = $('.js-checkbox');
  296 + var pushSwitch = document.getElementById("notifbox");
  297 + pushSwitch.disabled = true;
  298 +
252 299 enablePushSwitch.change(function(e) {
253 300 console.log('checkbox changed');
254 301 if (e.target.checked) {
... ... @@ -265,6 +312,7 @@
265 312 navigator.serviceWorker.register('service-worker.js')
266 313 .then(initialiseState);
267 314 } else {
  315 + // disable button if serviceworker is not available
268 316 // Service Workers aren't supported so you should hide the push UI
269 317 // If it's currently visible.
270 318 console.log('Ooops Service Workers aren\'t Supported',
templates/cardlist.html View file @ e91de4c
1 1 <body>
2   - <div class="row">
3   - <a class="btn" id="showHidden" ng-click="show = !show" style="margin-top: 15px">Show Hidden</a>
  2 +<div class="row">
  3 + <a class="btn" id="showHidden" ng-click="show = !show" style="margin-top: 15px">Show Hidden</a>
4 4  
5   - <div class="input-field col s6 right">
6   - <i class="mdi-action-search prefix"></i>
7   - <input id="search" type="text" class="validate" ng-model="searchText"/>
8   - <label for="search">Search</label>
9   - </div>
  5 + <div class="input-field col s6 right">
  6 + <i class="mdi-action-search prefix"></i>
  7 + <input id="search" type="text" class="validate" ng-model="searchText"/>
  8 + <label for="search">Search</label>
10 9 </div>
  10 +</div>
11 11  
12   - <div class="row">
13   - <form>
14   - <div class="col s12">
15   - <div class="col s2">
16   - <input type="checkbox" class="filled-in" id="weekOneCheck" ng-model="filter['week1']"/>
17   - <label for="weekOneCheck">Week One</label>
18   - </div>
19   - <div class="col s2">
20   - <input type="checkbox" class="filled-in" id="weekTwoCheck" ng-model="filter['week2']"/>
21   - <label for="weekTwoCheck">Week Two</label>
22   - </div>
23   - <div class="col s2">
24   - <input type="checkbox" class="filled-in" id="weekThreeCheck" ng-model="filter['week3']"/>
25   - <label for="weekThreeCheck">Week Three</label>
26   - </div>
27   - <div class="col s2">
28   - <input type="checkbox" class="filled-in" id="weekFourCheck" ng-model="filter['week4']"/>
29   - <label for="weekFourCheck">Week Four</label>
30   - </div>
31   - <div class="col s2">
32   - <input type="checkbox" class="filled-in" id="weekFiveCheck" ng-model="filter['week5']"/>
33   - <label for="weekFiveCheck">Week Five</label>
34   - </div>
  12 +<div class="row">
  13 + <form>
  14 + <div class="col s12">
  15 + <div class="col s2">
  16 + <input type="checkbox" class="filled-in" id="weekOneCheck" ng-model="filter['week1']"/>
  17 + <label for="weekOneCheck">Week One</label>
35 18 </div>
36   - <div class="col s12">
37   - <div class="col s2">
38   - <input type="checkbox" class="filled-in" id="weekSixCheck" ng-model="filter['week6']"/>
39   - <label for="weekSixCheck">Week Six</label>
40   - </div>
41   - <div class="col s2">
42   - <input type="checkbox" class="filled-in" id="weekSevenCheck" ng-model="filter['week7']"/>
43   - <label for="weekSevenCheck">Week Seven</label>
44   - </div>
45   - <div class="col s2">
46   - <input type="checkbox" class="filled-in" id="weekEightCheck" ng-model="filter['week8']"/>
47   - <label for="weekEightCheck">Week Eight</label>
48   - </div>
49   - <div class="col s2">
50   - <input type="checkbox" class="filled-in" id="weekNineCheck" ng-model="filter['week9']"/>
51   - <label for="weekNineCheck">Week Nine</label>
52   - </div>
53   - <div class="col s2">
54   - <input type="checkbox" class="filled-in" id="weekTenCheck" ng-model="filter['week10']"/>
55   - <label for="weekTenCheck">Week Ten</label>
56   - </div>
  19 + <div class="col s2">
  20 + <input type="checkbox" class="filled-in" id="weekTwoCheck" ng-model="filter['week2']"/>
  21 + <label for="weekTwoCheck">Week Two</label>
57 22 </div>
58   - </form>
59   - </div>
  23 + <div class="col s2">
  24 + <input type="checkbox" class="filled-in" id="weekThreeCheck" ng-model="filter['week3']"/>
  25 + <label for="weekThreeCheck">Week Three</label>
  26 + </div>
  27 + <div class="col s2">
  28 + <input type="checkbox" class="filled-in" id="weekFourCheck" ng-model="filter['week4']"/>
  29 + <label for="weekFourCheck">Week Four</label>
  30 + </div>
  31 + <div class="col s2">
  32 + <input type="checkbox" class="filled-in" id="weekFiveCheck" ng-model="filter['week5']"/>
  33 + <label for="weekFiveCheck">Week Five</label>
  34 + </div>
  35 + </div>
  36 + <div class="col s12">
  37 + <div class="col s2">
  38 + <input type="checkbox" class="filled-in" id="weekSixCheck" ng-model="filter['week6']"/>
  39 + <label for="weekSixCheck">Week Six</label>
  40 + </div>
  41 + <div class="col s2">
  42 + <input type="checkbox" class="filled-in" id="weekSevenCheck" ng-model="filter['week7']"/>
  43 + <label for="weekSevenCheck">Week Seven</label>
  44 + </div>
  45 + <div class="col s2">
  46 + <input type="checkbox" class="filled-in" id="weekEightCheck" ng-model="filter['week8']"/>
  47 + <label for="weekEightCheck">Week Eight</label>
  48 + </div>
  49 + <div class="col s2">
  50 + <input type="checkbox" class="filled-in" id="weekNineCheck" ng-model="filter['week9']"/>
  51 + <label for="weekNineCheck">Week Nine</label>
  52 + </div>
  53 + <div class="col s2">
  54 + <input type="checkbox" class="filled-in" id="weekTenCheck" ng-model="filter['week10']"/>
  55 + <label for="weekTenCheck">Week Ten</label>
  56 + </div>
  57 + </div>
  58 + </form>
  59 +</div>
60 60  
61   - <div class="list" style="padding: 0px 25px">
62   - <ul class="collection"
63   - ng-repeat="(weeknum, week_cards) in cards | filter:searchText | filter:filterByDate | groupBy: 'material_week_num'">
64   - <li class="collection-header"><h3>Week {{weeknum}}</h3></li>
65   - <li class="collection-item" ng-click="expand = !expand" ng-repeat="card in week_cards" ng-show="show || !card.is_hidden">
66   - <div>
67   - <span ng-bind-html="card | displayCard"></span>
68   - <span class="badge">{{dayofweek(card)}}</span>
69   - <p class="right-align" ng-show="expand">
70   - <a href="" class="tooltipped" ng-click="pull(card)" ng-show="!card.is_in_deck" data-position="bottom" data-delay="50" data-tooltip="Add to Deck">
71   - <i class="mdi-content-add-circle-outline small"></i></a>
72   - <a href="" class="tooltipped" ng-click="unpull(card)" ng-show="card.is_in_deck" data-position="bottom" data-delay="50" data-tooltip="Add to Deck">
73   - <i class="mdi-content-remove-circle-outline small"></i></a>
74   - <a href="" class="tooltipped" ng-click="hide(card)" ng-show="!card.is_hidden" data-position="bottom" data-delay="50" data-tooltip="Hide">
75   - <i class="mdi-action-visibility-off small"></i></a>
76   - <a href="" class="tooltipped" ng-click="unhide(card)" ng-show="card.is_hidden" data-position="bottom" data-delay="50" data-tooltip="Unhide">
77   - <i class="mdi-action-visibility small"></i></a>
78   - <a href="" ng-click="flag(card)" data-position="bottom" data-delay="50" data-tooltip="Flag">
79   - <i class="mdi-content-flag small"></i></a>
80   - </p>
81   - </div>
82   - </li>
83   - </ul>
84   - </div>
  61 +<div class="list" style="padding: 0px 25px">
  62 + <ul class="collection"
  63 + ng-repeat="(weeknum, week_cards) in cards | filter:searchText | filter:filterByDate | groupBy: 'material_week_num'">
  64 + <li class="collection-header"><h3>Week {{weeknum}}</h3></li>
  65 + <li class="collection-item" ng-click="expand = !expand" ng-repeat="card in week_cards"
  66 + ng-show="show || !card.is_hidden">
  67 + <i ng-show="card.isInDeck()" class="mdi-action-done small green-text"></i>
  68 + <span ng-bind-html="card | displayCard"></span>
  69 + <span class="badge">{{dayofweek(card)}}</span>
85 70  
  71 + <p class="right-align" ng-show="expand">
  72 + <a href="" class="tooltipped" ng-click="card.pull()" ng-show="!card.isInDeck()" data-position="bottom"
  73 + data-delay="50" data-tooltip="Add to Deck">
  74 + <i class="mdi-content-add-circle-outline small"></i></a>
  75 + <a href="" class="tooltipped" ng-click="card.unpull()" ng-show="card.isInDeck()" data-position="bottom"
  76 + data-delay="50" data-tooltip="Add to Deck">
  77 + <i class="mdi-content-remove-circle-outline small"></i></a>
  78 + <a href="" class="tooltipped" ng-click="card.hide()" ng-show="!card.is_hidden" data-position="bottom"
  79 + data-delay="50" data-tooltip="Hide">
  80 + <i class="mdi-action-visibility-off small"></i></a>
  81 + <a href="" class="tooltipped" ng-click="card.unhide()" ng-show="card.is_hidden" data-position="bottom"
  82 + data-delay="50" data-tooltip="Unhide">
  83 + <i class="mdi-action-visibility small"></i></a>
  84 + <a href="" ng-click="flag(card)" data-position="bottom" data-delay="50" data-tooltip="Flag">
  85 + <i class="mdi-content-flag small"></i></a>
  86 + </p>
  87 + </li>
  88 + </ul>
  89 +</div>
86 90  
87   - <div class="fixed-action-btn back-to-top" style="bottom: 45px; right: 24px; display: none;">
88   - <a class="btn-floating btn-large">
89   - <i class="mdi-editor-publish medium"></i>
90   - </a>
91   - </div>
  91 +
  92 +<div class="fixed-action-btn back-to-top" style="bottom: 45px; right: 24px; display: none;">
  93 + <a class="btn-floating btn-large">
  94 + <i class="mdi-editor-publish medium"></i>
  95 + </a>
  96 +</div>
92 97 </body>