diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto
index 04e172870..1df3bebf8 100644
--- a/pb/src/client_container.proto
+++ b/pb/src/client_container.proto
@@ -472,8 +472,8 @@ message TestClientMessage {
// sent from client to server periodically to let server track if the client is truly alive and avoid TCP timeout scenarios
// the server will send a HeartbeatAck in response to this
message Heartbeat {
- optional string notification_seen_at = 1;
-
+ optional string notification_seen = 1;
+ optional string notification_seen_at = 2;
}
// target: client
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 440613536..66f2ff37d 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -294,7 +294,28 @@ module JamRuby
# returns the # of new notifications
def new_notifications
- Notification.select('id').where(target_user_id: id).where('created_at > ?', notification_seen_at).count
+ search = Notification.select('id').where(target_user_id: self.id)
+ search = search.where('created_at > ?', self.notification_seen_at) if self.notification_seen_at
+ search.count
+ end
+
+ # the user can pass in a timestamp string, or the keyword 'LATEST'
+ # if LATEST is specified, we'll use the latest_notification as the timestamp
+ # if not, just use seen as-is
+ def update_notification_seen_at seen
+ new_latest_seen = nil
+ if seen == 'LATEST'
+ latest = self.latest_notification
+ new_latest_seen = latest.created_at if latest
+ else
+ new_latest_seen = seen
+ end
+
+ self.notification_seen_at = new_latest_seen
+ end
+
+ def latest_notification
+ Notification.select('created_at').where(target_user_id: id).limit(1).order('created_at DESC').first
end
def confirm_email!
diff --git a/web/app/assets/javascripts/AAB_message_factory.js b/web/app/assets/javascripts/AAB_message_factory.js
index 0b86c7900..04bd51344 100644
--- a/web/app/assets/javascripts/AAB_message_factory.js
+++ b/web/app/assets/javascripts/AAB_message_factory.js
@@ -99,9 +99,10 @@
};
// Heartbeat message
- factory.heartbeat = function(notification_last_seen_at) {
+ factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt) {
var data = {};
- data.notification_last_seen_at = notification_last_seen_at;
+ data.notification_seen = lastNotificationSeen;
+ data.notification_seen_at = lastNotificationSeenAt;
return client_container(msg.HEARTBEAT, route_to.SERVER, data);
};
diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js
index d78922ea7..f00e9b02d 100644
--- a/web/app/assets/javascripts/jamkazam.js
+++ b/web/app/assets/javascripts/jamkazam.js
@@ -29,6 +29,7 @@
var heartbeatAckCheckInterval = null;
var userDeferred = null;
var notificationLastSeenAt = undefined;
+ var notificationLastSeen = undefined;
var opts = {
inClient: true, // specify false if you want the app object but none of the client-oriented features
@@ -92,9 +93,9 @@
function _heartbeat() {
if (app.heartbeatActive) {
-
- var message = context.JK.MessageFactory.heartbeat(notificationLastSeenAt);
+ var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
notificationLastSeenAt = undefined;
+ notificationLastSeen = undefined;
context.JK.JamServer.send(message);
lastHeartbeatFound = false;
}
@@ -386,7 +387,7 @@
return userDeferred;
}
- this.updateNotificationSeen = function(notificationCreatedAt) {
+ this.updateNotificationSeen = function(notificationId, notificationCreatedAt) {
var time = new Date(notificationCreatedAt);
if(!notificationCreatedAt) {
@@ -395,10 +396,12 @@
if(!notificationLastSeenAt) {
notificationLastSeenAt = notificationCreatedAt;
+ notificationLastSeen = notificationId;
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
}
else if(time.getTime() > new Date(notificationLastSeenAt).getTime()) {
notificationLastSeenAt = notificationCreatedAt;
+ notificationLastSeen = notificationId;
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
}
else {
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index dc130dc78..555cc0d6d 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -251,7 +251,8 @@
if (!sidebarVisible) {
return;
}
- var $expandedPanelContents = $('[layout-id="' + expandedPanel + '"] [layout-panel="contents"]');
+ var $expandedPanel = $('[layout-id="' + expandedPanel + '"]');
+ var $expandedPanelContents = $expandedPanel.find('[layout-panel="contents"]');
var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight;
var searchHeight = $('.sidebar .search').first().height();
var expanderHeight = $('[layout-sidebar-expander]').height();
@@ -259,6 +260,7 @@
$('[layout-panel="contents"]').hide();
$('[layout-panel="contents"]').css({"height": "1px"});
$expandedPanelContents.show();
+ $expandedPanel.triggerHandler('open')
$expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration);
}
@@ -531,6 +533,25 @@
return openDialogs.length > 0;
}
+ function currentDialog() {
+ if(openDialogs.length == 0) return null;
+
+ return openDialogs[openDialogs.length - 1];
+ }
+
+ // payload is a notification event from websocket gateway
+ function dialogObscuredNotification(payload) {
+ var openDialog = currentDialog();
+ if(!openDialog) return false;
+
+ if(typeof openDialog.handledNotification === 'function') {
+ return !openDialog.handledNotification(payload);
+ }
+ else {
+ return true;
+ }
+ }
+
/**
* Responsible for keeping N dialogs in correct stacked order,
* also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one
@@ -854,6 +875,10 @@
showDialog(dialog, options);
};
+ this.dialogObscuredNotification = function() {
+ return dialogObscuredNotification();
+ }
+
this.isDialogShowing = function() {
return isDialogShowing();
}
diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js
index a5649a634..f3e46bfc2 100644
--- a/web/app/assets/javascripts/notificationPanel.js
+++ b/web/app/assets/javascripts/notificationPanel.js
@@ -10,34 +10,38 @@
var missedNotificationsWhileAway = false;
var $panel = null;
var $expanded = null;
+ var $contents = null;
var $count = null;
+ var $list = null;
+ var $notificationTemplate = null;
var darkenedColor = '#0D7B89';
var highlightedColor = 'white'
-
-
- // one important limitation; if the user is focused on an iframe, this will be false
- // however, if they are doing something with Facebook or the photo picker, this may actually still be desirable
- function userCanSeeNotifications() {
- return document.hasFocus() || app.layout.isDialogShowing();
- }
+ var textMessageDialog = null;
+ var queuedNotification = null;
+ var queuedNotificationCreatedAt = null;
function isNotificationsPanelVisible() {
- return $expanded.is(':visible')
+ return $contents.is(':visible')
}
function incrementNotificationCount() {
var count = parseInt($count.text());
- $count.text(count + 1);
+ setCount(count + 1);
}
// set the element to white, and pulse it down to the un-highlighted value 2x, then set
function pulseToDark() {
+ logger.debug("pulsing notification badge")
lowlightCount();
$count.pulse({'background-color' : highlightedColor}, {pulses: 2}, function() {
- $count.text('0');
+ $count.removeAttr('style')
+ setCount(0);
})
}
+ function setCount(count) {
+ $count.text(count);
+ }
function lowlightCount() {
$count.removeClass('highlighted');
@@ -47,11 +51,35 @@
$count.addClass('highlighted');
}
+ function queueNotificationSeen(notificationId, notificationCreatedAt) {
+
+ var time = new Date(notificationCreatedAt);
+
+ if(!notificationCreatedAt) {
+ throw 'invalid value passed to queuedNotificationCreatedAt'
+ }
+
+ if(!queuedNotificationCreatedAt) {
+ queuedNotification = notificationId;
+ queuedNotificationCreatedAt = notificationCreatedAt;
+ logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt);
+ }
+ else if(time.getTime() > new Date(queuedNotificationCreatedAt).getTime()) {
+ queuedNotification = notificationId;
+ queuedNotificationCreatedAt = notificationCreatedAt;
+ logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt);
+ }
+ else {
+ logger.debug("ignored queuedNotificationCreatedAt for: " + notificationCreatedAt);
+ }
+ }
+
function onNotificationOccurred(payload) {
- if(userCanSeeNotifications()) {
- app.updateNotificationSeen(payload.created_at);
+ if(userCanSeeNotifications(payload)) {
+ app.updateNotificationSeen(payload.notification_id, payload.created_at);
}
else {
+ queueNotificationSeen(payload.notification_id, payload.created_at);
highlightCount();
incrementNotificationCount();
missedNotificationsWhileAway = true;
@@ -63,12 +91,28 @@
if(missedNotificationsWhileAway) {
// catch user's eye, then put count to 0
pulseToDark();
+ if(queuedNotificationCreatedAt) {
+ app.updateNotificationSeen(queuedNotification, queuedNotificationCreatedAt);
+ }
}
}
+ queuedNotification = null;
+ queuedNotificationCreatedAt = null;
missedNotificationsWhileAway = false;
}
+ function opened() {
+ queuedNotification = null;
+ queuedNotificationCreatedAt = null;
+ rest.updateUser({notification_seen_at: 'LATEST'})
+ .done(function(response) {
+ lowlightCount();
+ setCount(0);
+ })
+ .fail(app.ajaxError)
+ }
+
function windowBlurred() {
}
@@ -77,6 +121,42 @@
$(app.layout).on('dialog_closed', function(e, data) {if(data.dialogCount == 0) userCameBack(); });
$(window).focus(userCameBack);
$(window).blur(windowBlurred);
+ app.user()
+ .done(function(user) {
+ setCount(user.new_notifications);
+ if(user.new_notifications > 0) {
+ highlightCount();
+ }
+ });
+
+ $panel.on('open', opened);
+
+ // friend notifications
+ registerFriendRequest();
+ registerFriendRequestAccepted();
+ registerNewUserFollower();
+ registerNewBandFollower();
+
+ // session notifications
+ registerSessionInvitation();
+ registerSessionEnded();
+ registerJoinRequest();
+ registerJoinRequestApproved();
+ registerJoinRequestRejected();
+ registerMusicianSessionJoin();
+ registerBandSessionJoin();
+
+ // recording notifications
+ registerMusicianRecordingSaved();
+ registerBandRecordingSaved();
+ registerRecordingMasterMixComplete();
+
+ // band notifications
+ registerBandInvitation();
+ registerBandInvitationAccepted();
+
+ // register text messages
+ registerTextMessage();
}
function populate() {
@@ -88,20 +168,736 @@
.fail(app.ajaxError)
}
- function initialize(sidebar) {
+ function updateNotificationList(response) {
+ $list.empty();
+
+ $.each(response, function(index, val) {
+
+ if(val.description == 'TEXT_MESSAGE') {
+ val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html();
+ }
+
+ // fill in template for Connect pre-click
+ var template = $notificationTemplate.html();
+ var notificationHtml = context.JK.fillTemplate(template, {
+ notificationId: val.notification_id,
+ sessionId: val.session_id,
+ avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
+ text: val.formatted_msg,
+ date: $.timeago(val.created_at)
+ });
+
+ $list.append(notificationHtml);
+
+ // val.description contains the notification record's description value from the DB (i.e., type)
+ initializeActions(val, val.description);
+ });
+ }
+
+
+ function initializeActions(payload, type) {
+
+ var $notification = $('li[notification-id=' + payload.notification_id + ']');
+ var $btnNotificationAction = '#btn-notification-action';
+
+ // wire up "x" button to delete notification
+ $notification.find('#img-delete-notification').click(deleteNotificationHandler);
+
+ // customize action buttons based on notification type
+ if (type === context.JK.MessageType.FRIEND_REQUEST) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('ACCEPT');
+ $action_btn.click(function() {
+ acceptFriendRequest(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) {
+ $notification.find('#div-actions').hide();
+ }
+
+ else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) {
+ $notification.find('#div-actions').hide();
+ }
+
+ else if (type === context.JK.MessageType.SESSION_INVITATION) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('JOIN');
+ $action_btn.click(function() {
+ openTerms(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.JOIN_REQUEST) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('APPROVE');
+ $action_btn.click(function() {
+ approveJoinRequest(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('JOIN');
+ $action_btn.click(function() {
+ openTerms(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) {
+ $notification.find('#div-actions').hide();
+ }
+
+ else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) {
+
+ var actionText = '';
+ var callback;
+ if (context.JK.currentUserMusician) {
+ // user is MUSICIAN; musician_access = TRUE
+ if (payload.musician_access) {
+ actionText = "JOIN";
+ callback = joinSession;
+ }
+ // user is MUSICIAN; fan_access = TRUE
+ else if (payload.fan_access) {
+ actionText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+ else {
+ // user is FAN; fan_access = TRUE
+ if (payload.fan_access) {
+ actionText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text(actionText);
+ $action_btn.click(function() {
+ callback(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('LISTEN');
+ $action_btn.click(function() {
+ listenToRecording(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) {
+ $notification.find('#div-actions').hide();
+ context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available
+ }
+
+ else if (type === context.JK.MessageType.BAND_INVITATION) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('ACCEPT');
+ $action_btn.click(function() {
+ acceptBandInvitation(payload);
+ });
+ }
+ else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) {
+ $notification.find('#div-actions').hide();
+ }
+ else if (type === context.JK.MessageType.TEXT_MESSAGE) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('REPLY');
+ $action_btn.click(function() {
+ var userId = $notification.find('.more-text-available').attr('data-sender-id');
+ app.layout.showDialog('text-message', { d1: userId });
+ });
+
+ var moreTextLink = $notification.find('.more-text-available');
+ var textMessage = $notification.find('.text-message');
+ var clipped_msg = textMessage.attr('data-is-clipped') === 'true';
+
+ if(clipped_msg) {
+ moreTextLink.text('more').show();
+ moreTextLink.click(function(e) {
+ var userId = $(this).attr('data-sender-id');
+
+ return false;
+ });
+ }
+ else {
+ moreTextLink.hide();
+ }
+ }
+ }
+
+ function acceptBandInvitation(args) {
+ rest.updateBandInvitation(
+ args.band_id,
+ args.band_invitation_id,
+ true
+ ).done(function(response) {
+ deleteNotification(args.notification_id); // delete notification corresponding to this friend request
+ }).error(app.ajaxError);
+ }
+
+
+ function deleteNotification(notificationId) {
+ var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
+ $.ajax({
+ type: "DELETE",
+ dataType: "json",
+ contentType: 'application/json',
+ url: url,
+ processData: false,
+ success: function(response) {
+ $('li[notification-id=' + notificationId + ']').hide();
+ //decrementNotificationCount();
+ },
+ error: app.ajaxError
+ });
+ }
+
+
+ function listenToSession(args) {
+ deleteNotification(args.notification_id);
+ context.JK.popExternalLink('/sessions/' + args.session_id);
+ }
+
+ /*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/
+ function joinSession(args) {
+ // NOTE: invited musicians get their own notification, so no need to check if user has invitation here
+ // like other places because an invited user would never get this notification
+ if (args.musician_access) {
+ if (args.approval_required) {
+ openAlert(args.session_id);
+ }
+ else {
+ openTerms(args);
+ }
+ }
+ deleteNotification(args.notification_id);
+ }
+
+
+ function registerJoinRequestApproved() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) {
+ logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Join Request Approved",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "JOIN SESSION",
+ "ok_callback": openTerms,
+ "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
+ });
+ });
+ }
+
+ function registerJoinRequestRejected() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) {
+ logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Join Request Rejected",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ function registerJoinRequest() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) {
+ logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Join Request",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "APPROVE",
+ "ok_callback": approveJoinRequest,
+ "ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id },
+ "cancel_text": "REJECT",
+ "cancel_callback": rejectJoinRequest,
+ "cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }
+ });
+ });
+ }
+
+
+ function registerFriendRequestAccepted() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) {
+ logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ initializeFriendsPanel();
+
+ app.notify({
+ "title": "Friend Request Accepted",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+ function registerNewUserFollower() {
+
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) {
+ logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Follower",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ function registerNewBandFollower() {
+
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) {
+ logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Band Follower",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ function registerFriendRequest() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
+ logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Friend Request",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "ACCEPT",
+ "ok_callback": acceptFriendRequest,
+ "ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id }
+ });
+ });
+ }
+
+ function acceptFriendRequest(args) {
+
+ rest.acceptFriendRequest({
+ status: 'accept',
+ friend_request_id: args.friend_request_id
+ }).done(function(response) {
+ deleteNotification(args.notification_id); // delete notification corresponding to this friend request
+ initializeFriendsPanel(); // refresh friends panel when request is accepted
+ }).error(app.ajaxError);
+ }
+
+ function registerSessionEnded() {
+ // TODO: this should clean up all notifications related to this session
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) {
+ logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload));
+ deleteSessionNotifications(payload.session_id);
+ });
+ }
+
+ // remove all notifications for this session
+ function deleteSessionNotifications(sessionId) {
+ $('li[session-id=' + sessionId + ']').hide();
+ //decrementNotificationCount();
+ }
+
+ function registerSessionInvitation() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) {
+ logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ var participants = [];
+ rest.getSession(payload.session_id).done(function(response) {
+ $.each(response.participants, function(index, val) {
+ participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name});
+ });
+
+ var participantHtml = "You have been invited to join a session with:
";
+ participantHtml += "
";
+
+ $.each(participants, function(index, val) {
+ if (index < 4) {
+ participantHtml += " + ") | " + val.name + " |
";
+ }
+ });
+
+ participantHtml += "
";
+
+ app.notify({
+ "title": "Session Invitation",
+ "text": participantHtml
+ }, {
+ "ok_text": "JOIN SESSION",
+ "ok_callback": openTerms,
+ "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
+ });
+ }).error(app.ajaxError);
+
+ });
+ }
+
+
+ function registerMusicianSessionJoin() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) {
+ logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload));
+
+ var okText = '';
+ var showNotification = false;
+ var callback;
+ if (context.JK.currentUserMusician) {
+ // user is MUSICIAN; musician_access = TRUE
+ if (payload.musician_access) {
+ showNotification = true;
+ okText = "JOIN";
+ callback = joinSession;
+ }
+ // user is MUSICIAN; fan_access = TRUE
+ else if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+ else {
+ // user is FAN; fan_access = TRUE
+ if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+
+ if (showNotification) {
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Musician Joined Session",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": okText,
+ "ok_callback": callback,
+ "ok_callback_args": {
+ "session_id": payload.session_id,
+ "fan_access": payload.fan_access,
+ "musician_access": payload.musician_access,
+ "approval_required": payload.approval_required,
+ "notification_id": payload.notification_id
+ }
+ }
+ );
+ }
+ });
+ }
+
+
+ function registerBandSessionJoin() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) {
+ logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload));
+
+ var okText = '';
+ var showNotification = false;
+ var callback;
+ if (context.JK.currentUserMusician) {
+ // user is MUSICIAN; musician_access = TRUE
+ if (payload.musician_access) {
+ showNotification = true;
+ okText = "JOIN";
+ callback = joinSession;
+ }
+ // user is MUSICIAN; fan_access = TRUE
+ else if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+ else {
+ // user is FAN; fan_access = TRUE
+ if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+
+ if (showNotification) {
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Joined Session",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "LISTEN",
+ "ok_callback": callback,
+ "ok_callback_args": {
+ "session_id": payload.session_id,
+ "fan_access": payload.fan_access,
+ "musician_access": payload.musician_access,
+ "approval_required": payload.approval_required,
+ "notification_id": payload.notification_id
+ }
+ }
+ );
+ }
+ });
+ }
+
+
+ function registerMusicianRecordingSaved() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) {
+ logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Musician Recording Saved",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "LISTEN",
+ "ok_callback": listenToRecording,
+ "ok_callback_args": {
+ "recording_id": payload.recording_id,
+ "notification_id": payload.notification_id
+ }
+ });
+ });
+ }
+
+ function registerBandRecordingSaved() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) {
+ logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Recording Saved",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "LISTEN",
+ "ok_callback": listenToRecording,
+ "ok_callback_args": {
+ "recording_id": payload.recording_id,
+ "notification_id": payload.notification_id
+ }
+ });
+ });
+ }
+
+
+ function registerRecordingMasterMixComplete() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) {
+ logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Recording Master Mix Complete",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "SHARE",
+ "ok_callback": shareRecording,
+ "ok_callback_args": {
+ "recording_id": payload.recording_id
+ }
+ });
+ });
+ }
+
+ function shareRecording(args) {
+ var recordingId = args.recording_id;
+ }
+
+ function registerBandInvitation() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) {
+ logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Invitation",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "ACCEPT",
+ "ok_callback": acceptBandInvitation,
+ "ok_callback_args": {
+ "band_invitation_id": payload.band_invitation_id,
+ "band_id": payload.band_id,
+ "notification_id": payload.notification_id
+ }
+ });
+ });
+ }
+
+
+ function registerTextMessage() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) {
+ logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload));
+
+ textMessageDialog.messageReceived(payload);
+
+ handleNotification(payload, header.type);
+ });
+ }
+
+ function registerBandInvitationAccepted() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) {
+ logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Invitation Accepted",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ // one important limitation; if the user is focused on an iframe, this will be false
+ // however, if they are doing something with Facebook or the photo picker, this may actually still be desirable
+ function userCanSeeNotifications(payload) {
+ return document.hasFocus() && !app.layout.dialogObscuredNotification(payload);
+ }
+
+ // default handler for incoming notification
+ function handleNotification(payload, type) {
+
+ // on a load of notifications, it is possible to load a very new notification,
+ // and get a websocket notification right after for that same notification,
+ // so we need to protect against such duplicates
+ if($list.find('li[notification-id="' + payload.notification_id + '"]').length > 0) {
+ return false;
+ }
+
+ // add notification to sidebar
+ var template = $notificationTemplate.html();
+ var notificationHtml = context.JK.fillTemplate(template, {
+ notificationId: payload.notification_id,
+ sessionId: payload.session_id,
+ avatar_url: context.JK.resolveAvatarUrl(payload.photo_url),
+ text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg ,
+ date: $.timeago(payload.created_at)
+ });
+
+ $list.prepend(notificationHtml);
+
+ onNotificationOccurred(payload);
+
+ initializeActions(payload, type);
+
+ return true;
+ }
+
+
+ function onCreateJoinRequest(sessionId) {
+ var joinRequest = {};
+ joinRequest.music_session = sessionId;
+ joinRequest.user = context.JK.currentUserId;
+ rest.createJoinRequest(joinRequest)
+ .done(function(response) {
+
+ }).error(context.JK.app.ajaxError);
+
+ context.JK.app.layout.closeDialog('alert');
+ }
+
+ function approveJoinRequest(args) {
+ rest.updateJoinRequest(args.join_request_id, true)
+ .done(function(response) {
+ deleteNotification(args.notification_id);
+ }).error(app.ajaxError);
+ }
+
+ function rejectJoinRequest(args) {
+ rest.updateJoinRequest(args.join_request_id, false)
+ .done(function(response) {
+ deleteNotification(args.notification_id);
+ }).error(app.ajaxError);
+ }
+
+ function openTerms(args) {
+ var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted);
+ termsDialog.initialize();
+ app.layout.showDialog('terms');
+ }
+
+ function onTermsAccepted(args) {
+ deleteNotification(args.notification_id);
+ context.location = '/client#/session/' + args.session_id;
+ }
+
+
+ function openAlert(sessionId) {
+ var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES",
+ "You must be approved to join this session. Would you like to send a request to join?",
+ sessionId, onCreateJoinRequest);
+
+ alertDialog.initialize();
+ context.JK.app.layout.showDialog('alert');
+ }
+
+ function listenToRecording(args) {
+ deleteNotification(args.notification_id);
+ context.JK.popExternalLink('/recordings/' + args.recording_id);
+ }
+
+ function deleteNotificationHandler(evt) {
+ evt.stopPropagation();
+ var notificationId = $(this).attr('notification-id');
+ deleteNotification(notificationId);
+ }
+
+ function initialize(sidebar, textMessageDialogInstance) {
+ textMessageDialog = textMessageDialogInstance;
$panel = $('[layout-id="panelNotifications"]');
$expanded = $panel.find('.panel.expanded');
+ $contents = $panel.find('.panelcontents');
$count = $panel.find('#sidebar-notification-count');
+ $list = $panel.find('#sidebar-notification-list');
+ $notificationTemplate = $('#template-notification-panel');
if($panel.length == 0) throw "notifications panel not found"
if($expanded.length == 0) throw "notifications expanded content not found"
+ if($contents.length == 0) throw "notifications contents not found"
if($count.length == 0) throw "notifications count element not found";
+ if($list.length == 0) throw "notification list element not found";
+ if($notificationTemplate.length == 0) throw "notification template not found";
events();
populate();
};
- this.initiliaze = initialize;
+ this.initialize = initialize;
this.onNotificationOccurred = onNotificationOccurred;
};
})(window, jQuery);
diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js
index 2e90b7c5e..f4a4a2624 100644
--- a/web/app/assets/javascripts/sidebar.js
+++ b/web/app/assets/javascripts/sidebar.js
@@ -10,6 +10,7 @@
var invitationDialog = null;
var textMessageDialog = null;
var notificationPanel = null;
+ var me = null;
function initializeSearchPanel() {
$('#search_text_type').change(function() {
@@ -118,188 +119,7 @@
function initializeNotificationsPanel() {
notificationPanel = new context.JK.NotificationPanel(app);
- notificationPanel.initialize();
- }
-
- function updateNotificationList(response) {
- $('#sidebar-notification-list').empty();
-
- $.each(response, function(index, val) {
-
- if(val.description == 'TEXT_MESSAGE') {
- val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html();
- }
-
- // fill in template for Connect pre-click
- var template = $('#template-notification-panel').html();
- var notificationHtml = context.JK.fillTemplate(template, {
- notificationId: val.notification_id,
- sessionId: val.session_id,
- avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
- text: val.formatted_msg,
- date: $.timeago(val.created_at)
- });
-
- $('#sidebar-notification-list').append(notificationHtml);
-
- // val.description contains the notification record's description value from the DB (i.e., type)
- initializeActions(val, val.description);
- });
- }
-
- function initializeActions(payload, type) {
-
- var $notification = $('li[notification-id=' + payload.notification_id + ']');
- var $btnNotificationAction = '#btn-notification-action';
-
- // wire up "x" button to delete notification
- $notification.find('#img-delete-notification').click(deleteNotificationHandler);
-
- // customize action buttons based on notification type
- if (type === context.JK.MessageType.FRIEND_REQUEST) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('ACCEPT');
- $action_btn.click(function() {
- acceptFriendRequest(payload);
- });
- }
-
- else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) {
- $notification.find('#div-actions').hide();
- }
-
- else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) {
- $notification.find('#div-actions').hide();
- }
-
- else if (type === context.JK.MessageType.SESSION_INVITATION) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('JOIN');
- $action_btn.click(function() {
- openTerms(payload);
- });
- }
-
- else if (type === context.JK.MessageType.JOIN_REQUEST) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('APPROVE');
- $action_btn.click(function() {
- approveJoinRequest(payload);
- });
- }
-
- else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('JOIN');
- $action_btn.click(function() {
- openTerms(payload);
- });
- }
-
- else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) {
- $notification.find('#div-actions').hide();
- }
-
- else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) {
-
- var actionText = '';
- var callback;
- if (context.JK.currentUserMusician) {
- // user is MUSICIAN; musician_access = TRUE
- if (payload.musician_access) {
- actionText = "JOIN";
- callback = joinSession;
- }
- // user is MUSICIAN; fan_access = TRUE
- else if (payload.fan_access) {
- actionText = "LISTEN";
- callback = listenToSession;
- }
- }
- else {
- // user is FAN; fan_access = TRUE
- if (payload.fan_access) {
- actionText = "LISTEN";
- callback = listenToSession;
- }
- }
-
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text(actionText);
- $action_btn.click(function() {
- callback(payload);
- });
- }
-
- else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('LISTEN');
- $action_btn.click(function() {
- listenToRecording(payload);
- });
- }
-
- else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) {
- $notification.find('#div-actions').hide();
- context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available
- }
-
- else if (type === context.JK.MessageType.BAND_INVITATION) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('ACCEPT');
- $action_btn.click(function() {
- acceptBandInvitation(payload);
- });
- }
- else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) {
- $notification.find('#div-actions').hide();
- }
- else if (type === context.JK.MessageType.TEXT_MESSAGE) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('REPLY');
- $action_btn.click(function() {
- var userId = $notification.find('.more-text-available').attr('data-sender-id');
- app.layout.showDialog('text-message', { d1: userId });
- });
-
- var moreTextLink = $notification.find('.more-text-available');
- var textMessage = $notification.find('.text-message');
- var clipped_msg = textMessage.attr('data-is-clipped') === 'true';
-
- if(clipped_msg) {
- moreTextLink.text('more').show();
- moreTextLink.click(function(e) {
- var userId = $(this).attr('data-sender-id');
-
- return false;
- });
- }
- else {
- moreTextLink.hide();
- }
- }
- }
-
- function deleteNotificationHandler(evt) {
- evt.stopPropagation();
- var notificationId = $(this).attr('notification-id');
- deleteNotification(notificationId);
- }
-
- function deleteNotification(notificationId) {
- var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
- $.ajax({
- type: "DELETE",
- dataType: "json",
- contentType: 'application/json',
- url: url,
- processData: false,
- success: function(response) {
- $('li[notification-id=' + notificationId + ']').hide();
- decrementNotificationCount();
- },
- error: app.ajaxError
- });
+ notificationPanel.initialize(me, textMessageDialog);
}
function initializeChatPanel() {
@@ -348,61 +168,6 @@
$('#sidebar-search-results').height('0px');
}
- function decrementNotificationCount() {
- /**
- var count = parseInt($('#sidebar-notification-count').html());
- if (count === 0) {
- $('#sidebar-notification-count').html(0);
- }
- else {
- $('#sidebar-notification-count').html(count - 1);
- }
- */
- }
-
- // one important limitation; if the user is focused on an iframe, this will be false
- // however, if they are doing something with Facebook or the photo picker, this may actually still be desirable
- function userCanSeeNotifications() {
- return document.hasFocus() || app.layout.isDialogShowing();
- }
-
- // default handler for incoming notification
- function handleNotification(payload, type) {
-
- // on a load of notifications, it is possible to load a very new notification,
- // and get a websocket notification right after for that same notification,
- // so we need to protect against such duplicates
- if($('#sidebar-notification-list').find('li[notification-id="' + payload.notification_id + '"]').length > 0) {
- return false;
- }
-
- // increment displayed notification count
- incrementNotificationCount();
-
- // add notification to sidebar
- var template = $("#template-notification-panel").html();
- var notificationHtml = context.JK.fillTemplate(template, {
- notificationId: payload.notification_id,
- sessionId: payload.session_id,
- avatar_url: context.JK.resolveAvatarUrl(payload.photo_url),
- text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg ,
- date: $.timeago(payload.created_at)
- });
-
- $('#sidebar-notification-list').prepend(notificationHtml);
-
- if(userCanSeeNotifications()) {
- app.updateNotificationSeen(payload.created_at);
- }
- else {
-
- }
-
- initializeActions(payload, type);
-
- return true;
- }
-
var delay = (function(){
var timer = 0;
return function(callback, ms) {
@@ -460,32 +225,14 @@
// friend notifications
registerFriendUpdate();
- registerFriendRequest();
- registerFriendRequestAccepted();
- registerNewUserFollower();
- registerNewBandFollower();
// session invitations
- registerSessionInvitation();
- registerSessionEnded();
- registerJoinRequest();
- registerJoinRequestApproved();
- registerJoinRequestRejected();
registerSessionJoin();
registerSessionDepart();
- registerMusicianSessionJoin();
- registerBandSessionJoin();
// recording notifications
- registerMusicianRecordingSaved();
- registerBandRecordingSaved();
registerRecordingStarted();
registerRecordingEnded();
- registerRecordingMasterMixComplete();
-
- // band notifications
- registerBandInvitation();
- registerBandInvitationAccepted();
// broadcast notifications
registerSourceUpRequested();
@@ -493,9 +240,6 @@
registerSourceUp();
registerSourceDown();
- // register text messages
- registerTextMessage();
-
// watch for Invite More Users events
$('#sidebar-div .btn-email-invitation').click(function() {
invitationDialog.showEmailDialog();
@@ -529,210 +273,6 @@
});
}
- function registerFriendRequest() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
- logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Friend Request",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "ACCEPT",
- "ok_callback": acceptFriendRequest,
- "ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id }
- });
- });
- }
-
- function acceptFriendRequest(args) {
-
- rest.acceptFriendRequest({
- status: 'accept',
- friend_request_id: args.friend_request_id
- }).done(function(response) {
- deleteNotification(args.notification_id); // delete notification corresponding to this friend request
- initializeFriendsPanel(); // refresh friends panel when request is accepted
- }).error(app.ajaxError);
- }
-
- function registerFriendRequestAccepted() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) {
- logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- initializeFriendsPanel();
-
- app.notify({
- "title": "Friend Request Accepted",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
- function registerNewUserFollower() {
-
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) {
- logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Follower",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
- function registerNewBandFollower() {
-
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) {
- logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Band Follower",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
-
- }
-
- function registerSessionInvitation() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) {
- logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- var participants = [];
- rest.getSession(payload.session_id).done(function(response) {
- $.each(response.participants, function(index, val) {
- participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name});
- });
-
- var participantHtml = "You have been invited to join a session with:
";
- participantHtml += "";
-
- $.each(participants, function(index, val) {
- if (index < 4) {
- participantHtml += " + ") | " + val.name + " |
";
- }
- });
-
- participantHtml += "
";
-
- app.notify({
- "title": "Session Invitation",
- "text": participantHtml
- }, {
- "ok_text": "JOIN SESSION",
- "ok_callback": openTerms,
- "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
- });
- }).error(app.ajaxError);
-
- });
- }
-
- function openTerms(args) {
- var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted);
- termsDialog.initialize();
- app.layout.showDialog('terms');
- }
-
- function onTermsAccepted(args) {
- deleteNotification(args.notification_id);
- context.location = '/client#/session/' + args.session_id;
- }
-
- function registerSessionEnded() {
- // TODO: this should clean up all notifications related to this session
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) {
- logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload));
- deleteSessionNotifications(payload.session_id);
- });
- }
-
- // remove all notifications for this session
- function deleteSessionNotifications(sessionId) {
- $('li[session-id=' + sessionId + ']').hide();
- decrementNotificationCount();
- }
-
- function registerJoinRequest() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) {
- logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Join Request",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "APPROVE",
- "ok_callback": approveJoinRequest,
- "ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id },
- "cancel_text": "REJECT",
- "cancel_callback": rejectJoinRequest,
- "cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }
- });
- });
- }
-
- function approveJoinRequest(args) {
- rest.updateJoinRequest(args.join_request_id, true)
- .done(function(response) {
- deleteNotification(args.notification_id);
- }).error(app.ajaxError);
- }
-
- function rejectJoinRequest(args) {
- rest.updateJoinRequest(args.join_request_id, false)
- .done(function(response) {
- deleteNotification(args.notification_id);
- }).error(app.ajaxError);
- }
-
- function registerJoinRequestApproved() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) {
- logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Join Request Approved",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "JOIN SESSION",
- "ok_callback": openTerms,
- "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
- });
- });
- }
-
- function registerJoinRequestRejected() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) {
- logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Join Request Rejected",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
function registerSessionJoin() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, function(header, payload) {
logger.debug("Handling SESSION_JOIN msg " + JSON.stringify(payload));
@@ -765,201 +305,6 @@
});
}
- function registerMusicianSessionJoin() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) {
- logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload));
-
- var okText = '';
- var showNotification = false;
- var callback;
- if (context.JK.currentUserMusician) {
- // user is MUSICIAN; musician_access = TRUE
- if (payload.musician_access) {
- showNotification = true;
- okText = "JOIN";
- callback = joinSession;
- }
- // user is MUSICIAN; fan_access = TRUE
- else if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
- else {
- // user is FAN; fan_access = TRUE
- if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
-
- if (showNotification) {
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Musician Joined Session",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": okText,
- "ok_callback": callback,
- "ok_callback_args": {
- "session_id": payload.session_id,
- "fan_access": payload.fan_access,
- "musician_access": payload.musician_access,
- "approval_required": payload.approval_required,
- "notification_id": payload.notification_id
- }
- }
- );
- }
- });
- }
-
- function registerBandSessionJoin() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) {
- logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload));
-
- var okText = '';
- var showNotification = false;
- var callback;
- if (context.JK.currentUserMusician) {
- // user is MUSICIAN; musician_access = TRUE
- if (payload.musician_access) {
- showNotification = true;
- okText = "JOIN";
- callback = joinSession;
- }
- // user is MUSICIAN; fan_access = TRUE
- else if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
- else {
- // user is FAN; fan_access = TRUE
- if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
-
- if (showNotification) {
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Joined Session",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "LISTEN",
- "ok_callback": callback,
- "ok_callback_args": {
- "session_id": payload.session_id,
- "fan_access": payload.fan_access,
- "musician_access": payload.musician_access,
- "approval_required": payload.approval_required,
- "notification_id": payload.notification_id
- }
- }
- );
- }
- });
- }
-
- function listenToSession(args) {
- deleteNotification(args.notification_id);
- context.JK.popExternalLink('/sessions/' + args.session_id);
- }
-
- /*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/
- function joinSession(args) {
- // NOTE: invited musicians get their own notification, so no need to check if user has invitation here
- // like other places because an invited user would never get this notification
- if (args.musician_access) {
- if (args.approval_required) {
- openAlert(args.session_id);
- }
- else {
- openTerms(args);
- }
- }
- deleteNotification(args.notification_id);
- }
-
- function openAlert(sessionId) {
- var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES",
- "You must be approved to join this session. Would you like to send a request to join?",
- sessionId, onCreateJoinRequest);
-
- alertDialog.initialize();
- context.JK.app.layout.showDialog('alert');
- }
-
- function onCreateJoinRequest(sessionId) {
- var joinRequest = {};
- joinRequest.music_session = sessionId;
- joinRequest.user = context.JK.currentUserId;
- rest.createJoinRequest(joinRequest)
- .done(function(response) {
-
- }).error(context.JK.app.ajaxError);
-
- context.JK.app.layout.closeDialog('alert');
- }
- //////////////////////////////////////////////////////////////////////////////////////////
-
- function registerMusicianRecordingSaved() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) {
- logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Musician Recording Saved",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "LISTEN",
- "ok_callback": listenToRecording,
- "ok_callback_args": {
- "recording_id": payload.recording_id,
- "notification_id": payload.notification_id
- }
- });
- });
- }
-
- function registerBandRecordingSaved() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) {
- logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Recording Saved",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "LISTEN",
- "ok_callback": listenToRecording,
- "ok_callback_args": {
- "recording_id": payload.recording_id,
- "notification_id": payload.notification_id
- }
- });
- });
- }
-
- function listenToRecording(args) {
- deleteNotification(args.notification_id);
- context.JK.popExternalLink('/recordings/' + args.recording_id);
- }
-
function registerRecordingStarted() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_STARTED, function(header, payload) {
logger.debug("Handling RECORDING_STARTED msg " + JSON.stringify(payload));
@@ -984,86 +329,6 @@
});
}
- function registerRecordingMasterMixComplete() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) {
- logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Recording Master Mix Complete",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "SHARE",
- "ok_callback": shareRecording,
- "ok_callback_args": {
- "recording_id": payload.recording_id
- }
- });
- });
- }
-
- function shareRecording(args) {
- var recordingId = args.recording_id;
- }
-
- function registerBandInvitation() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) {
- logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Invitation",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "ACCEPT",
- "ok_callback": acceptBandInvitation,
- "ok_callback_args": {
- "band_invitation_id": payload.band_invitation_id,
- "band_id": payload.band_id,
- "notification_id": payload.notification_id
- }
- });
- });
- }
-
- function acceptBandInvitation(args) {
- rest.updateBandInvitation(
- args.band_id,
- args.band_invitation_id,
- true
- ).done(function(response) {
- deleteNotification(args.notification_id); // delete notification corresponding to this friend request
- }).error(app.ajaxError);
- }
-
- function registerTextMessage() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) {
- logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload));
-
- textMessageDialog.messageReceived(payload);
-
- handleNotification(payload, header.type);
- });
- }
-
- function registerBandInvitationAccepted() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) {
- logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Invitation Accepted",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
function registerSourceUpRequested() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SOURCE_UP_REQUESTED, function(header, payload) {
@@ -1170,13 +435,13 @@
}
this.initialize = function(invitationDialogInstance, textMessageDialogInstance) {
+ invitationDialog = invitationDialogInstance;
+ textMessageDialog = textMessageDialogInstance;
events();
initializeSearchPanel();
initializeFriendsPanel();
initializeChatPanel();
initializeNotificationsPanel();
- invitationDialog = invitationDialogInstance;
- textMessageDialog = textMessageDialogInstance;
};
}
})(window,jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/textMessageDialog.js b/web/app/assets/javascripts/textMessageDialog.js
index e76d413ba..3bcc2c5c2 100644
--- a/web/app/assets/javascripts/textMessageDialog.js
+++ b/web/app/assets/javascripts/textMessageDialog.js
@@ -47,8 +47,7 @@
return message;
}
- function sendMessage(e) {
-
+ function sendMessage() {
var msg = $textBox.val();
if(!msg || msg == '') {
// don't bother the server with empty messages
@@ -124,6 +123,15 @@
return markedUpMsg;
}
+ // we handled the notification, meaning the dialog showed this message as a chat message
+ function handledNotification(payload) {
+ return showing && payload.description == "TEXT_MESSAGE" && payload.sender_id == otherId;
+ }
+
+ function afterShow(args) {
+ $textBox.focus();
+ }
+
function beforeShow(args) {
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
@@ -175,13 +183,38 @@
reset();
}
- function postMessage(e) {
+ function pasteIntoInput(el, text) {
+ el.focus();
+ if (typeof el.selectionStart == "number"
+ && typeof el.selectionEnd == "number") {
+ var val = el.value;
+ var selStart = el.selectionStart;
+ el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd);
+ el.selectionEnd = el.selectionStart = selStart + text.length;
+ } else if (typeof document.selection != "undefined") {
+ var textRange = document.selection.createRange();
+ textRange.text = text;
+ textRange.collapse(false);
+ textRange.select();
+ }
+ }
- return false;
+ function handleEnter(evt) {
+ if (evt.keyCode == 13 && evt.shiftKey) {
+ pasteIntoInput(this, "\n");
+ evt.preventDefault();
+ }
+ else if(evt.keyCode == 13 && !evt.shiftKey){
+ sendMessage();
+ return false;
+ }
}
function events() {
- $form.submit(postMessage)
+ $form.submit(sendMessage)
+
+ // http://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea
+ $textBox.keydown(handleEnter);
}
@@ -234,6 +267,7 @@
function initialize() {
var dialogBindings = {
'beforeShow' : beforeShow,
+ 'afterShow' : afterShow,
'afterHide': afterHide
};
@@ -253,6 +287,7 @@
this.initialize = initialize;
this.messageReceived = messageReceived;
this.formatTextMessage = formatTextMessage;
+ this.handledNotification = handledNotification;
}
return this;
diff --git a/web/app/assets/stylesheets/client/textMessageDialog.css.scss b/web/app/assets/stylesheets/client/textMessageDialog.css.scss
index 04f837e53..5605adfce 100644
--- a/web/app/assets/stylesheets/client/textMessageDialog.css.scss
+++ b/web/app/assets/stylesheets/client/textMessageDialog.css.scss
@@ -29,6 +29,7 @@
.previous-message-text {
line-height:18px;
+ white-space:pre-line;
}
.previous-message-timestamp {
@@ -52,4 +53,9 @@
width:100%;
height:40px;
}
+
+ .btn-send-text-message {
+ text-align:center;
+ width:50px;
+ }
}
\ No newline at end of file
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index ea3d28c47..5dc55cf9c 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -44,6 +44,12 @@ class ApiUsersController < ApiController
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
@user.biography = params[:biography] if params.has_key?(:biography)
+
+ # allow keyword of 'LATEST' to mean set the notification_seen_at to the most recent notification for this user
+ if params.has_key?(:notification_seen_at)
+ @user.update_notification_seen_at params[:notification_seen_at]
+ end
+
@user.save
if @user.errors.any?
diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb
index 348cf7469..6eb930565 100644
--- a/web/app/controllers/users_controller.rb
+++ b/web/app/controllers/users_controller.rb
@@ -214,11 +214,6 @@ class UsersController < ApplicationController
@promo_latest, start = Feed.index(nil, limit: 10)
end
- # temporary--will go away soon
- @jamfest_2014 = Event.find_by_id('80bb6acf-3ddc-4305-9442-75e6ec047c27')
- @jamfest_2014 = Event.find_by_id('a2dfbd26-9b17-4446-8c61-b67a542ea6ee') unless @jamfest_2014
- # temporary--end
-
@welcome_page = true
render :layout => "web"
end
diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl
index c42d940b6..0b1c30540 100644
--- a/web/app/views/api_music_sessions/show.rabl
+++ b/web/app/views/api_music_sessions/show.rabl
@@ -1,4 +1,4 @@
-object @music_session
+ object @music_session
if !current_user
# there should be more data returned, but we need to think very carefully about what data is public for a music session
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 02e1a0176..d28fd6ae5 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -311,10 +311,6 @@
}
JK.bindHoverEvents();
-
- setInterval(function() {
- console.log("IN FOCUS: " + document.hasFocus());
- }, 1000)
})
diff --git a/web/app/views/users/welcome.html.haml b/web/app/views/users/welcome.html.haml
index f2d987044..823f9d23c 100644
--- a/web/app/views/users/welcome.html.haml
+++ b/web/app/views/users/welcome.html.haml
@@ -7,10 +7,6 @@
= link_to "Already have an account?", signin_path, class: "signin", id: "signin"
- content_for :after_black_bar do
- - if @jamfest_2014
- .jamfest{style: 'top:-70px;position:relative'}
- %a{ href: event_path(@jamfest_2014.slug), style: 'font-size:24px' }
- Join us online March 12 for Virtual Jam Fest!
%div{style: "padding-top:20px;"}
.right
= render :partial => "buzz"
diff --git a/web/spec/features/home_spec.rb b/web/spec/features/home_spec.rb
index 1585f9c73..7c206be4b 100644
--- a/web/spec/features/home_spec.rb
+++ b/web/spec/features/home_spec.rb
@@ -8,6 +8,7 @@ describe "Home Screen", :js => true, :type => :feature, :capybara_feature => tru
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
+ MusicSession.delete_all
end
let(:user) { FactoryGirl.create(:user) }
diff --git a/web/spec/features/notification_highlighter_spec.rb b/web/spec/features/notification_highlighter_spec.rb
new file mode 100644
index 000000000..d75946605
--- /dev/null
+++ b/web/spec/features/notification_highlighter_spec.rb
@@ -0,0 +1,168 @@
+require 'spec_helper'
+
+describe "Notification Highlighter", :js => true, :type => :feature, :capybara_feature => true do
+
+ subject { page }
+
+ before(:all) do
+ Capybara.javascript_driver = :poltergeist
+ Capybara.current_driver = Capybara.javascript_driver
+ Capybara.default_wait_time = 10
+ end
+
+ let(:user) { FactoryGirl.create(:user) }
+ let(:user2) { FactoryGirl.create(:user) }
+
+
+ shared_examples_for :notification_badge do |options|
+ it "in correct state" do
+ sign_in_poltergeist(user) unless page.has_selector?('h2', 'musicians')
+ badge = find("#{NOTIFICATION_PANEL} .badge", text:options[:count])
+ badge['class'].include?('highlighted').should == options[:highlighted]
+
+ if options[:action] == :click
+ badge.trigger(:click)
+ badge = find("#{NOTIFICATION_PANEL} .badge", text:0)
+ badge['class'].include?('highlighted').should == false
+ end
+
+ end
+ end
+
+
+ describe "user with no notifications" do
+
+ it_behaves_like :notification_badge, highlighted: false, count:0
+
+ describe "and realtime notifications with sidebar closed" do
+ before(:each) do
+ sign_in_poltergeist(user)
+ document_focus
+ user.reload
+ end
+
+ it_behaves_like :notification_badge, highlighted: false, count:0
+
+ describe "sees notification" do
+ before(:each) do
+ notification = Notification.send_text_message("text message", user2, user)
+ notification.errors.any?.should be_false
+ end
+
+ it_behaves_like :notification_badge, highlighted: false, count:0, action: :click
+ end
+
+ describe "document out of focus" do
+ before(:each) do
+ document_blur
+ notification = Notification.send_text_message("text message", user2, user)
+ notification.errors.any?.should be_false
+ end
+
+ it_behaves_like :notification_badge, highlighted: true, count:1, action: :click
+ end
+ end
+
+
+ describe "and realtime notifications with sidebar open" do
+ before(:each) do
+ # generate one message so that count = 1 to start
+ notification = Notification.send_text_message("text message", user2, user)
+ notification.errors.any?.should be_false
+ sign_in_poltergeist(user)
+ document_focus
+ open_notifications
+ badge = find("#{NOTIFICATION_PANEL} .badge", text:'0') # wait for the opening of the sidebar to bring count to 0
+ user.reload
+ end
+
+ it_behaves_like :notification_badge, highlighted: false, count:0
+
+ describe "sees notification" do
+ before(:each) do
+ notification = Notification.send_text_message("text message", user2, user)
+ notification.errors.any?.should be_false
+ find('#notification #ok-button') # wait for notification to show, so that we know the sidebar had a chance to update
+ end
+
+ it_behaves_like :notification_badge, highlighted: false, count:0
+ end
+
+ describe "document out of focus" do
+ before(:each) do
+ document_blur
+ notification = Notification.send_text_message("text message 2", user2, user)
+ notification.errors.any?.should be_false
+ find('#notification #ok-button')
+ end
+
+ it_behaves_like :notification_badge, highlighted: true, count:1
+
+ describe "user comes back" do
+ before(:each) do
+ window_focus
+
+ it_behaves_like :notification_badge, highlighted: false, count:1
+ end
+ end
+ end
+ end
+ end
+
+ describe "user with new notifications" do
+ before(:each) do
+ # create a notification
+ notification = Notification.send_text_message("text message", user2, user)
+ notification.errors.any?.should be_false
+ end
+
+ it_behaves_like :notification_badge, highlighted:true, count:1, action: :click
+
+ describe "user has previously seen notifications" do
+ before(:each) do
+ user.update_notification_seen_at 'LATEST'
+ user.save!
+ end
+
+ it_behaves_like :notification_badge, highlighted: false, count:0, action: :click
+
+ describe "user again has unseen notifications" do
+ before(:each) do
+ # create a notification
+ notification = Notification.send_text_message("text message", user2, user)
+ notification.errors.any?.should be_false
+ end
+
+ it_behaves_like :notification_badge, highlighted:true, count:1, action: :click
+ end
+ end
+ end
+
+
+ describe "user no unseen notifications" do
+ describe "notification occurs in realtime" do
+
+ describe "sidebar is open" do
+ describe "user can see notifications" do
+ it "stays deactivated" do
+
+ end
+ end
+
+ describe "user can not see notifications" do
+ describe "with dialog open" do
+ it "temporarily activates" do
+
+ end
+ end
+
+ describe "with document blurred" do
+ it "temporarily activates" do
+
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/web/spec/features/text_message_spec.rb b/web/spec/features/text_message_spec.rb
index 601eb7c93..517b135da 100644
--- a/web/spec/features/text_message_spec.rb
+++ b/web/spec/features/text_message_spec.rb
@@ -42,7 +42,7 @@ describe "Text Message", :js => true, :type => :feature, :capybara_feature => tr
notification = Notification.send_text_message("bibbity bobbity boo", @user2, @user1)
notification.errors.any?.should be_false
- open_sidebar
+ open_notifications
# find the notification and click REPLY
find("[layout-id='panelNotifications'] [notification-id='#{notification.id}'] .button-orange", text:'REPLY').trigger(:click)
diff --git a/web/spec/support/client_interactions.rb b/web/spec/support/client_interactions.rb
index 8b4bc2121..a02c12026 100644
--- a/web/spec/support/client_interactions.rb
+++ b/web/spec/support/client_interactions.rb
@@ -1,6 +1,8 @@
# methods here all assume you are in /client
+NOTIFICATION_PANEL = '[layout-id="panelNotifications"]'
+
# enters text into the search sidebar
def site_search(text, options = {})
within('#searchForm') do
@@ -56,11 +58,41 @@ def send_text_message(msg, options={})
end
end
-def open_sidebar
- find('[layout-id="panelNotifications"] .panel-header').trigger(:click)
+def open_notifications
+ find("#{NOTIFICATION_PANEL} .panel-header").trigger(:click)
end
def hover_intent(element)
element.hover
element.hover
+end
+
+# forces document.hasFocus() to return false
+def document_blur
+ page.evaluate_script(%{(function() {
+ // save original
+ if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
+
+ window.document.hasFocus = function() {
+ console.log("document.hasFocus() returns false");
+ return false;
+ }
+ })()})
+end
+
+def document_focus
+ page.evaluate_script(%{(function() {
+ // save original
+ if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
+
+ window.document.hasFocus = function() {
+ console.log("document.hasFocus() returns true");
+ return true;
+ }
+ })()})
+end
+
+# simulates focus event on window
+def window_focus
+ page.evaluate_script(%{window.jQuery(window).trigger('focus');})
end
\ No newline at end of file
diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb
index 866429bfb..0f3c8b0e0 100644
--- a/websocket-gateway/lib/jam_websockets/router.rb
+++ b/websocket-gateway/lib/jam_websockets/router.rb
@@ -575,20 +575,9 @@ module JamWebsockets
connection.touch
# update user's notification_seen_at field if the heartbeat indicates it saw one
- notification_seen_at_parsed = nil
- notification_seen_at = heartbeat.notification_seen_at if heartbeat.value_for_tag(1)
- begin
- notification_seen_at_parsed = Time.parse(notification_seen_at)
- rescue Exception => e
- @log.error "unable to parse notification_seen_at in heartbeat from #{context}. notification_seen_at: #{notification_seen_at}"
- end
-
- if notification_seen_at_parsed
- connection.user.notification_seen_at = notification_seen_at
- unless connection.user.save(validate: false)
- @log.error "unable to update notification_seen_at for client #{context}. errors: #{connection.user.errors.inspect}"
- end
- end
+ # first we try to use the notification id, which should usually exist.
+ # if not, then fallback to notification_seen_at, which is approximately the last time we saw a notification
+ update_notification_seen_at(connection, context, heartbeat)
end
ConnectionManager.active_record_transaction do |connection_manager|
@@ -614,6 +603,34 @@ module JamWebsockets
end
end
+ def update_notification_seen_at(connection, context, heartbeat)
+ notification_id_field = heartbeat.notification_seen if heartbeat.value_for_tag(1)
+ if notification_id_field
+ notification = Notification.find_by_id(notification_id_field)
+ if notification
+ connection.user.notification_seen_at = notification.created_at
+ unless connection.user.save(validate: false)
+ @log.error "unable to update notification_seen_at via id field for client #{context}. errors: #{connection.user.errors.inspect}"
+ end
+ else
+ notification_seen_at_parsed = nil
+ notification_seen_at = heartbeat.notification_seen_at if heartbeat.value_for_tag(2)
+ begin
+ notification_seen_at_parsed = Time.parse(notification_seen_at) if notification_seen_at && notification_seen_at.length > 0
+ rescue Exception => e
+ @log.error "unable to parse notification_seen_at in heartbeat from #{context}. notification_seen_at: #{notification_seen_at}"
+ end
+
+ if notification_seen_at_parsed
+ connection.user.notification_seen_at = notification_seen_at
+ unless connection.user.save(validate: false)
+ @log.error "unable to update notification_seen_at via time field for client #{context}. errors: #{connection.user.errors.inspect}"
+ end
+ end
+ end
+ end
+ end
+
def valid_login(username, password, token, client_id)
if !token.nil? && token != ''