* VRFS-871 - user-to-user messaging complete
This commit is contained in:
parent
b783d270b1
commit
b04c7bcea5
|
|
@ -136,4 +136,5 @@ events.sql
|
|||
cascading_delete_constraints_for_release.sql
|
||||
events_social_description.sql
|
||||
fix_broken_cities.sql
|
||||
notifications_with_text.sql
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE notifications ADD COLUMN message TEXT;
|
||||
|
|
@ -49,6 +49,9 @@ message ClientMessage {
|
|||
BAND_INVITATION = 225;
|
||||
BAND_INVITATION_ACCEPTED = 230;
|
||||
BAND_SESSION_JOIN = 235;
|
||||
|
||||
// text message
|
||||
TEXT_MESSAGE = 236;
|
||||
|
||||
MUSICIAN_SESSION_FRESH = 240;
|
||||
MUSICIAN_SESSION_STALE = 245;
|
||||
|
|
@ -125,6 +128,9 @@ message ClientMessage {
|
|||
optional BandInvitationAccepted band_invitation_accepted = 230;
|
||||
optional BandSessionJoin band_session_join = 235;
|
||||
|
||||
// text message
|
||||
optional TextMessage text_message = 236;
|
||||
|
||||
optional MusicianSessionFresh musician_session_fresh = 240;
|
||||
optional MusicianSessionStale musician_session_stale = 245;
|
||||
|
||||
|
|
@ -381,6 +387,16 @@ message BandSessionJoin {
|
|||
optional string created_at = 8;
|
||||
}
|
||||
|
||||
message TextMessage {
|
||||
optional string photo_url = 1;
|
||||
optional string sender_name = 2;
|
||||
optional string sender_id = 3;
|
||||
optional string msg = 4;
|
||||
optional string notification_id = 5;
|
||||
optional string created_at = 6;
|
||||
optional bool clipped_msg = 7;
|
||||
}
|
||||
|
||||
// route_to: client:
|
||||
// sent by server to let the rest of the participants know a client has become active again after going stale
|
||||
message MusicianSessionFresh {
|
||||
|
|
|
|||
|
|
@ -233,6 +233,24 @@
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def text_message(email, sender_id, sender_name, sender_photo_url, message)
|
||||
subject = "Message from #{sender_name}"
|
||||
unique_args = {:type => "text_message"}
|
||||
|
||||
@note = message
|
||||
@root_url = APP_CONFIG.external_root_url
|
||||
@sender_id = sender_id
|
||||
@sender_name = sender_name
|
||||
@sender_photo_url = sender_photo_url
|
||||
sendgrid_category "Notification"
|
||||
sendgrid_unique_args :type => unique_args[:type]
|
||||
mail(:to => email, :subject => subject) do |format|
|
||||
format.text
|
||||
format.html { render :layout => "from_user_mailer" }
|
||||
end
|
||||
end
|
||||
|
||||
# def send_notification(email, subject, msg, unique_args)
|
||||
# @body = msg
|
||||
# sendgrid_category "Notification"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<% provide(:title, "Message from #{@sender_name}") %>
|
||||
<% provide(:photo_url, @sender_photo_url) %>
|
||||
|
||||
<% content_for :note do %>
|
||||
<%= @note %>
|
||||
|
||||
<p>To reply to this message, <a href="<%= @root_url %>/client#/home/text-message/d1=<%= @sender_id %>">click here</a>.</p>
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<%= @sender_name %> says: <%= @note %>
|
||||
|
||||
To reply to this message, click here: <%= @root_url %>/client#/home/text-message/d1=<%= @sender_id %>
|
||||
|
|
@ -30,4 +30,7 @@ module NotificationTypes
|
|||
BAND_INVITATION_ACCEPTED = "BAND_INVITATION_ACCEPTED"
|
||||
BAND_SESSION_JOIN = "BAND_SESSION_JOIN" # cleared using SESSION_ENDED notification
|
||||
|
||||
# general purpose text message
|
||||
TEXT_MESSAGE = "TEXT_MESSAGE"
|
||||
|
||||
end
|
||||
|
|
@ -75,6 +75,8 @@ module ValidationMessages
|
|||
MUST_BE_A_MUSICIAN = "must be a musician"
|
||||
CLAIMED_RECORDING_ALREADY_IN_PROGRESS = "already started by someone else"
|
||||
|
||||
# notification
|
||||
DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender'
|
||||
|
||||
# takes either a string/string hash, or a string/array-of-strings|symbols hash,
|
||||
# and creates a ActiveRecord.errors style object
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
module JamRuby
|
||||
# if a bad argument is supplied.
|
||||
# Why not use the default ruby argument error? Using this one allows us to know our API layer threw this, versus us using some core library incorrectly
|
||||
# So why not use the default ruby argument error? Using this one allows us to know our API layer threw this, versus us using some core library incorrectly
|
||||
class JamArgumentError < ArgumentError
|
||||
|
||||
attr_accessor :field, :field_message
|
||||
|
||||
def initialize(message, field = nil)
|
||||
@message = message
|
||||
@field_message = message
|
||||
@field = field
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -64,6 +64,8 @@ module JamWebEventMachine
|
|||
|
||||
def self.run
|
||||
Thread.new do
|
||||
#ActiveRecord::Base.connection.disconnect!
|
||||
ActiveRecord::Base.establish_connection
|
||||
run_em
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ end
|
|||
class NoProfanityValidator < ActiveModel::EachValidator
|
||||
# implement the method called during validation
|
||||
def validate_each(record, attribute, value)
|
||||
record.errors[attribute] << 'Cannot contain profanity' if Profanity.is_profane?(value)
|
||||
record.errors[attribute] << 'cannot contain profanity' if Profanity.is_profane?(value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -560,6 +560,25 @@ module JamRuby
|
|||
)
|
||||
end
|
||||
|
||||
# creates the general purpose text message
|
||||
def text_message(receiver_id, sender_photo_url, sender_name, sender_id, msg, clipped_msg, notification_id, created_at)
|
||||
text_message = Jampb::TextMessage.new(
|
||||
:photo_url => sender_photo_url,
|
||||
:sender_name => sender_name,
|
||||
:sender_id => sender_id,
|
||||
:msg => msg,
|
||||
:clipped_msg => clipped_msg,
|
||||
:notification_id => notification_id,
|
||||
:created_at => created_at
|
||||
)
|
||||
|
||||
Jampb::ClientMessage.new(
|
||||
:type => ClientMessage::Type::TEXT_MESSAGE,
|
||||
:route_to => USER_TARGET_PREFIX + receiver_id,
|
||||
:text_message => text_message
|
||||
)
|
||||
end
|
||||
|
||||
# create a musician fresh session message
|
||||
def musician_session_fresh(session_id, user_id, username, photo_url)
|
||||
fresh = Jampb::MusicianSessionFresh.new(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ module JamRuby
|
|||
# ensure recipient is a Musician
|
||||
user = User.find(user_id)
|
||||
unless user.musician?
|
||||
raise JamRuby::JamArgumentError, BAND_INVITATION_FAN_RECIPIENT_ERROR
|
||||
raise JamRuby::JamArgumentError.new(BAND_INVITATION_FAN_RECIPIENT_ERROR, :receiver)
|
||||
end
|
||||
|
||||
band_invitation.band_id = band_id
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module JamRuby
|
|||
validates :email, format: {with: VALID_EMAIL_REGEX}, :if => lambda { |iu| iu.email_required? }
|
||||
validates :autofriend, :inclusion => {:in => [nil, true, false]}
|
||||
validates :invitation_code, :presence => true
|
||||
validates :note, length: {maximum: 400}, no_profanity: true # 400 == arbitrary.
|
||||
validates :note, length: {maximum: 1000}, no_profanity: true # 1000 == arbitrary.
|
||||
|
||||
validate :one_facebook_invite_per_user, :if => lambda { |iu| iu.facebook_invite? }
|
||||
validate :valid_personalized_invitation
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ module JamRuby
|
|||
belongs_to :recording, :class_name => "JamRuby::Recording", :foreign_key => "recording_id"
|
||||
|
||||
validates :target_user, :presence => true
|
||||
validates :message, length: {minimum: 1, maximum: 400}, no_profanity: true, if: :text_message?
|
||||
validate :different_source_target, if: :text_message?
|
||||
|
||||
def different_source_target
|
||||
unless target_user_id.nil? || source_user_id.nil?
|
||||
errors.add(:target_user, ValidationMessages::DIFFERENT_SOURCE_TARGET) if target_user_id == source_user_id
|
||||
end
|
||||
end
|
||||
|
||||
def index(user_id)
|
||||
Notification.where(:target_user_id => user_id).limit(50)
|
||||
|
|
@ -42,7 +50,7 @@ module JamRuby
|
|||
band = Band.find(self.band_id)
|
||||
end
|
||||
|
||||
return self.class.format_msg(self.description, source_user, band)
|
||||
self.class.format_msg(self.description, source_user, band)
|
||||
end
|
||||
|
||||
# TODO: MAKE ALL METHODS BELOW ASYNC SO THE CLIENT DOESN'T BLOCK ON NOTIFICATION LOGIC
|
||||
|
|
@ -719,6 +727,38 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def send_text_message(message, sender, receiver)
|
||||
|
||||
notification = Notification.new
|
||||
notification.description = NotificationTypes::TEXT_MESSAGE
|
||||
notification.message = message
|
||||
notification.source_user_id = sender.id
|
||||
notification.target_user_id = receiver.id if receiver
|
||||
if notification.save
|
||||
if receiver.online
|
||||
clip_at = 200
|
||||
msg_is_clipped = message.length > clip_at
|
||||
truncated_msg = message[0..clip_at - 1]
|
||||
msg = @@message_factory.text_message(
|
||||
receiver.id,
|
||||
sender.photo_url,
|
||||
sender.name,
|
||||
sender.id,
|
||||
truncated_msg,
|
||||
msg_is_clipped,
|
||||
notification.id,
|
||||
notification.created_date)
|
||||
|
||||
@@mq_router.publish_to_user(receiver.id, msg)
|
||||
|
||||
else
|
||||
UserMailer.text_message(receiver.email, sender.id, sender.name, sender.resolved_photo_url, message).deliver
|
||||
end
|
||||
end
|
||||
|
||||
notification
|
||||
end
|
||||
|
||||
def send_band_invitation(band, band_invitation, sender, receiver)
|
||||
|
||||
notification = Notification.new
|
||||
|
|
@ -830,5 +870,11 @@ module JamRuby
|
|||
@@mq_router.server_publish_to_everyone_in_session(music_session, msg)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def text_message?
|
||||
description == 'TEXT_MESSAGE'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -424,4 +424,12 @@ FactoryGirl.define do
|
|||
|
||||
factory :event_session, :class => JamRuby::EventSession do
|
||||
end
|
||||
|
||||
factory :notification, :class => JamRuby::Notification do
|
||||
|
||||
factory :notification_text_message do
|
||||
description 'TEXT_MESSAGE'
|
||||
message Faker::Lorem.characters(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Notification do
|
||||
|
||||
before(:each) do
|
||||
UserMailer.deliveries.clear
|
||||
|
||||
end
|
||||
|
||||
describe "send_text_message" do
|
||||
it "success when offline" do
|
||||
receiver = FactoryGirl.create(:user)
|
||||
sender = FactoryGirl.create(:user)
|
||||
message = "Just a test message!"
|
||||
|
||||
called_count = 0
|
||||
MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
|
||||
called_count += 1
|
||||
end
|
||||
|
||||
notification = Notification.send_text_message(message, sender, receiver)
|
||||
|
||||
notification.errors.any?.should be_false
|
||||
UserMailer.deliveries.length.should == 1
|
||||
called_count.should == 0
|
||||
end
|
||||
|
||||
|
||||
it "success when online" do
|
||||
receiver = FactoryGirl.create(:user)
|
||||
receiver_connection = FactoryGirl.create(:connection, user: receiver)
|
||||
sender = FactoryGirl.create(:user)
|
||||
|
||||
message = "Just a test message!"
|
||||
|
||||
called_count = 0
|
||||
saved_msg = nil
|
||||
MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
|
||||
saved_msg = msg
|
||||
called_count += 1
|
||||
end
|
||||
|
||||
notification = Notification.send_text_message(message, sender, receiver)
|
||||
|
||||
notification.errors.any?.should be_false
|
||||
UserMailer.deliveries.length.should == 0
|
||||
called_count.should == 1
|
||||
saved_msg.text_message.msg.should == message
|
||||
saved_msg.text_message.photo_url.should == ''
|
||||
saved_msg.text_message.sender_name.should == sender.name
|
||||
saved_msg.text_message.notification_id.should == notification.id
|
||||
saved_msg.text_message.created_at = notification.created_date
|
||||
saved_msg.text_message.clipped_msg.should be_false
|
||||
end
|
||||
|
||||
it "success when online with long message" do
|
||||
receiver = FactoryGirl.create(:user)
|
||||
receiver_connection = FactoryGirl.create(:connection, user: receiver)
|
||||
sender = FactoryGirl.create(:user)
|
||||
|
||||
message = "0" * 203 # 200 is clip size
|
||||
|
||||
called_count = 0
|
||||
saved_msg = nil
|
||||
MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
|
||||
saved_msg = msg
|
||||
called_count += 1
|
||||
end
|
||||
|
||||
notification = Notification.send_text_message(message, sender, receiver)
|
||||
|
||||
notification.errors.any?.should be_false
|
||||
UserMailer.deliveries.length.should == 0
|
||||
called_count.should == 1
|
||||
saved_msg.text_message.msg.should == "0" * 200
|
||||
saved_msg.text_message.photo_url.should == ''
|
||||
saved_msg.text_message.sender_name.should == sender.name
|
||||
saved_msg.text_message.notification_id.should == notification.id
|
||||
saved_msg.text_message.created_at = notification.created_date
|
||||
saved_msg.text_message.clipped_msg.should be_true
|
||||
end
|
||||
|
||||
it "fails with profanity" do
|
||||
receiver = FactoryGirl.create(:user)
|
||||
sender = FactoryGirl.create(:user)
|
||||
message = "ass"
|
||||
|
||||
called_count = 0
|
||||
MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
|
||||
called_count += 1
|
||||
end
|
||||
|
||||
notification = Notification.send_text_message(message, sender, receiver)
|
||||
|
||||
notification.errors.any?.should be_true
|
||||
notification.errors[:message].should == ['cannot contain profanity']
|
||||
UserMailer.deliveries.length.should == 0
|
||||
called_count.should == 0
|
||||
end
|
||||
|
||||
it "fails when target is same as receiver" do
|
||||
receiver = FactoryGirl.create(:user)
|
||||
sender = FactoryGirl.create(:user)
|
||||
message = "yo"
|
||||
|
||||
called_count = 0
|
||||
MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
|
||||
called_count += 1
|
||||
end
|
||||
|
||||
notification = Notification.send_text_message(message, sender, sender)
|
||||
|
||||
notification.errors.any?.should be_true
|
||||
notification.errors[:target_user].should == [ValidationMessages::DIFFERENT_SOURCE_TARGET]
|
||||
UserMailer.deliveries.length.should == 0
|
||||
called_count.should == 0
|
||||
end
|
||||
|
||||
it "fails when there is no message" do
|
||||
receiver = FactoryGirl.create(:user)
|
||||
sender = FactoryGirl.create(:user)
|
||||
message = ''
|
||||
|
||||
called_count = 0
|
||||
MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
|
||||
called_count += 1
|
||||
end
|
||||
|
||||
notification = Notification.send_text_message(message, sender, receiver)
|
||||
|
||||
notification.errors.any?.should be_true
|
||||
notification.errors[:message].should == ['is too short (minimum is 1 characters)']
|
||||
UserMailer.deliveries.length.should == 0
|
||||
called_count.should == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -31,6 +31,12 @@ describe "RenderMailers", :slow => true do
|
|||
it { @filename="password_changed"; UserMailer.password_changed(user).deliver }
|
||||
it { @filename="updated_email"; UserMailer.updated_email(user).deliver }
|
||||
it { @filename="updating_email"; UserMailer.updating_email(user).deliver }
|
||||
|
||||
describe "has sending user" do
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
it { @filename="text_message"; UserMailer.text_message(user.email, user2.id, user2.name, user2.resolved_photo_url, 'Get online!!').deliver }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "InvitedUserMailer emails" do
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@
|
|||
BAND_INVITATION : "BAND_INVITATION",
|
||||
BAND_INVITATION_ACCEPTED : "BAND_INVITATION_ACCEPTED",
|
||||
|
||||
// text message
|
||||
TEXT_MESSAGE : "TEXT_MESSAGE",
|
||||
|
||||
// broadcast notifications
|
||||
SOURCE_UP_REQUESTED : "SOURCE_UP_REQUESTED",
|
||||
SOURCE_DOWN_REQUESTED : "SOURCE_DOWN_REQUESTED",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
|
||||
function handleDeleteAudioProfile(audioProfileId) {
|
||||
console.log("deleting audio profile: " + audioProfileId);
|
||||
logger.debug("deleting audio profile: " + audioProfileId);
|
||||
|
||||
context.jamClient.TrackDeleteProfile(audioProfileId);
|
||||
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@
|
|||
|
||||
function regionListFailure(jqXHR, textStatus, errorThrown) {
|
||||
if(jqXHR.status == 422) {
|
||||
console.log("no regions found for country: " + recentUserDetail.country);
|
||||
logger.debug("no regions found for country: " + recentUserDetail.country);
|
||||
}
|
||||
else {
|
||||
app.ajaxError(arguments);
|
||||
|
|
@ -218,7 +218,7 @@
|
|||
|
||||
function cityListFailure(jqXHR, textStatus, errorThrown) {
|
||||
if(jqXHR.status == 422) {
|
||||
console.log("no cities found for country/region: " + recentUserDetail.country + "/" + recentUserDetail.state);
|
||||
logger.debug("no cities found for country/region: " + recentUserDetail.country + "/" + recentUserDetail.state);
|
||||
}
|
||||
else {
|
||||
app.ajaxError(arguments);
|
||||
|
|
@ -410,7 +410,7 @@
|
|||
}
|
||||
|
||||
function updateCityList(selectedCountry, selectedRegion, cityElement) {
|
||||
console.log("updating city list: selectedCountry %o, selectedRegion %o", selectedCountry, selectedRegion);
|
||||
logger.debug("updating city list: selectedCountry %o, selectedRegion %o", selectedCountry, selectedRegion);
|
||||
|
||||
// only update cities
|
||||
if (selectedCountry && selectedRegion) {
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@
|
|||
self.updatingAvatar = true;
|
||||
renderAvatarSpinner();
|
||||
|
||||
console.log("Converting...");
|
||||
logger.debug("Converting...");
|
||||
|
||||
// we convert two times; first we crop to the selected region,
|
||||
// then we scale to 88x88 (targetCropSize X targetCropSize), which is the largest size we use throughout the site.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
//= require jquery.infinitescroll
|
||||
//= require jquery.hoverIntent
|
||||
//= require jquery.dotdotdot
|
||||
//= require AAA_Log
|
||||
//= require globals
|
||||
//= require AAB_message_factory
|
||||
//= require AAC_underscore
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@
|
|||
self.updatingBandPhoto = true;
|
||||
renderBandPhotoSpinner();
|
||||
|
||||
console.log("Converting...");
|
||||
logger.debug("Converting...");
|
||||
|
||||
// we convert two times; first we crop to the selected region,
|
||||
// then we scale to 88x88 (targetCropSize X targetCropSize), which is the largest size we use throughout the site.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
function cancelUpdate(e) {
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode == 78) {
|
||||
console.log("update canceled!");
|
||||
logger.debug("update canceled!");
|
||||
app.layout.closeDialog('client-update');
|
||||
app.clientUpdating = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//= require jquery
|
||||
//= require jquery.queryparams
|
||||
//= require AAA_Log
|
||||
//= require AAC_underscore
|
||||
//= require globals
|
||||
//= require jamkazam
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
function handle_fblogin_response(response) {
|
||||
|
||||
console.log("facebook login response: status=" + response.status)
|
||||
logger.debug("facebook login response: status=" + response.status)
|
||||
|
||||
if(response.status == "connected") {
|
||||
connected = true;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
var instrument_logo_map = context.JK.getInstrumentIconMap24();
|
||||
var did_show_musician_page = false;
|
||||
var page_num=1, page_count=0;
|
||||
var textMessageDialog = null;
|
||||
|
||||
function loadMusicians(queryString) {
|
||||
// squelch nulls and undefines
|
||||
|
|
@ -86,27 +87,27 @@
|
|||
var mTemplate = $('#template-find-musician-row').html();
|
||||
var fTemplate = $('#template-musician-follow-info').html();
|
||||
var aTemplate = $('#template-musician-action-btns').html();
|
||||
var mVals, mm, renderings='';
|
||||
var mVals, musician, renderings='';
|
||||
var instr_logos, instr;
|
||||
var follows, followVals, aFollow;
|
||||
|
||||
for (ii=0, len=musicians.length; ii < len; ii++) {
|
||||
mm = musicians[ii];
|
||||
if (context.JK.currentUserId === mm.id) {
|
||||
musician = musicians[ii];
|
||||
if (context.JK.currentUserId === musician.id) {
|
||||
// VRFS-294.3 (David) => skip if current user is musician
|
||||
continue;
|
||||
}
|
||||
instr_logos = '';
|
||||
for (var jj=0, ilen=mm['instruments'].length; jj<ilen; jj++) {
|
||||
if (mm['instruments'][jj].instrument_id in instrument_logo_map) {
|
||||
instr = instrument_logo_map[mm['instruments'][jj].instrument_id];
|
||||
for (var jj=0, ilen=musician['instruments'].length; jj<ilen; jj++) {
|
||||
if (musician['instruments'][jj].instrument_id in instrument_logo_map) {
|
||||
instr = instrument_logo_map[musician['instruments'][jj].instrument_id];
|
||||
}
|
||||
instr_logos += '<img src="' + instr + '"/>';
|
||||
}
|
||||
follows = '';
|
||||
followVals = {};
|
||||
for (var jj=0, ilen=mm['followings'].length; jj<ilen; jj++) {
|
||||
aFollow = mm['followings'][jj];
|
||||
for (var jj=0, ilen=musician['followings'].length; jj<ilen; jj++) {
|
||||
aFollow = musician['followings'][jj];
|
||||
followVals = {
|
||||
user_id: aFollow.user_id,
|
||||
musician_name: aFollow.name,
|
||||
|
|
@ -117,27 +118,29 @@
|
|||
if (2 == jj) break;
|
||||
}
|
||||
var actionVals = {
|
||||
profile_url: "/client#/profile/" + mm.id,
|
||||
friend_class: 'button-' + (mm['is_friend'] ? 'grey' : 'orange'),
|
||||
friend_caption: (mm.is_friend ? 'DIS':'')+'CONNECT',
|
||||
follow_class: 'button-' + (mm['is_following'] ? 'grey' : 'orange'),
|
||||
follow_caption: (mm.is_following ? 'UN':'')+'FOLLOW',
|
||||
profile_url: "/client#/profile/" + musician.id,
|
||||
friend_class: 'button-' + (musician['is_friend'] ? 'grey' : 'orange'),
|
||||
friend_caption: (musician.is_friend ? 'DIS':'')+'CONNECT',
|
||||
follow_class: 'button-' + (musician['is_following'] ? 'grey' : 'orange'),
|
||||
follow_caption: (musician.is_following ? 'UN':'')+'FOLLOW',
|
||||
message_class: 'button-orange',
|
||||
message_caption: 'MESSAGE',
|
||||
button_message: 'button-orange'
|
||||
};
|
||||
var musician_actions = context.JK.fillTemplate(aTemplate, actionVals);
|
||||
|
||||
mVals = {
|
||||
avatar_url: context.JK.resolveAvatarUrl(mm.photo_url),
|
||||
profile_url: "/client#/profile/" + mm.id,
|
||||
musician_name: mm.name,
|
||||
musician_location: mm.city + ', ' + mm.state,
|
||||
avatar_url: context.JK.resolveAvatarUrl(musician.photo_url),
|
||||
profile_url: "/client#/profile/" + musician.id,
|
||||
musician_name: musician.name,
|
||||
musician_location: musician.city + ', ' + musician.state,
|
||||
instruments: instr_logos,
|
||||
biography: mm['biography'],
|
||||
follow_count: mm['follow_count'],
|
||||
friend_count: mm['friend_count'],
|
||||
recording_count: mm['recording_count'],
|
||||
session_count: mm['session_count'],
|
||||
musician_id: mm['id'],
|
||||
biography: musician['biography'],
|
||||
follow_count: musician['follow_count'],
|
||||
friend_count: musician['friend_count'],
|
||||
recording_count: musician['recording_count'],
|
||||
session_count: musician['session_count'],
|
||||
musician_id: musician['id'],
|
||||
musician_follow_template: follows,
|
||||
musician_action_template: musician_actions
|
||||
};
|
||||
|
|
@ -148,6 +151,7 @@
|
|||
|
||||
$('.search-m-friend').on('click', friendMusician);
|
||||
$('.search-m-follow').on('click', followMusician);
|
||||
$('.search-m-message').on('click', messageMusician);
|
||||
|
||||
context.JK.bindHoverEvents();
|
||||
}
|
||||
|
|
@ -163,7 +167,7 @@
|
|||
|
||||
function clearResults() {
|
||||
musicians = {};
|
||||
$('#musician-filter-results').empty();
|
||||
$('#musician-filter-results .musician-list-result').remove();
|
||||
page_num = 1;
|
||||
page_count = 0;
|
||||
}
|
||||
|
|
@ -217,6 +221,12 @@
|
|||
});
|
||||
}
|
||||
|
||||
function messageMusician() {
|
||||
var userId = $(this).parent().data('musician-id');
|
||||
app.layout.showDialog('text-message', { d1: userId });
|
||||
return false;
|
||||
}
|
||||
|
||||
function events() {
|
||||
$('#musician_query_distance').change(refreshDisplay);
|
||||
$('#musician_instrument').change(refreshDisplay);
|
||||
|
|
@ -228,11 +238,17 @@
|
|||
page_num += 1;
|
||||
search();
|
||||
}
|
||||
else {
|
||||
$('#end-of-musician-list').show()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
function initialize(textMessageDialogInstance) {
|
||||
|
||||
textMessageDialog = textMessageDialogInstance;
|
||||
|
||||
var screenBindings = {
|
||||
'beforeShow': beforeShow,
|
||||
'afterShow': afterShow
|
||||
|
|
|
|||
|
|
@ -692,7 +692,7 @@
|
|||
}
|
||||
|
||||
function newFtueSave(persist) {
|
||||
console.log("newFtueSave persist(" + persist + ")")
|
||||
logger.debug("newFtueSave persist(" + persist + ")")
|
||||
newFtueUpdateLatencyView('loading');
|
||||
logger.debug("Calling FTUESave(" + persist + ")");
|
||||
jamClient.FTUESave(persist);
|
||||
|
|
|
|||
|
|
@ -21,16 +21,16 @@
|
|||
function switchClientMode(e) {
|
||||
// ctrl + shift + 0
|
||||
if(e.ctrlKey && e.shiftKey && e.keyCode == 48) {
|
||||
console.log("switch client mode!");
|
||||
logger.debug("switch client mode!");
|
||||
var act_as_native_client = $.cookie('act_as_native_client');
|
||||
|
||||
console.log("currently: " + act_as_native_client);
|
||||
logger.debug("currently: " + act_as_native_client);
|
||||
if(act_as_native_client == null || act_as_native_client != "true") {
|
||||
console.log("forcing act as native client!");
|
||||
logger.debug("forcing act as native client!");
|
||||
$.cookie('act_as_native_client', 'true', { expires: 120, path: '/' });
|
||||
}
|
||||
else {
|
||||
console.log("remove act as native client!");
|
||||
logger.debug("remove act as native client!");
|
||||
$.removeCookie('act_as_native_client');
|
||||
}
|
||||
window.location.reload();
|
||||
|
|
|
|||
|
|
@ -108,11 +108,13 @@
|
|||
function configureActionButtons(user) {
|
||||
var btnFriendSelector = "#btnFriend";
|
||||
var btnFollowSelector = "#btnFollow";
|
||||
var btnMessageSelector = '#btnMessage';
|
||||
|
||||
// if unauthenticated or authenticated user is viewing his own profile
|
||||
if (!context.JK.currentUserId || context.JK.currentUserId === user.id) {
|
||||
$(btnFriendSelector, hoverSelector).hide();
|
||||
$(btnFollowSelector, hoverSelector).hide();
|
||||
$(btnMessageSelector, hoverSelector).hide();
|
||||
}
|
||||
else {
|
||||
if (user.is_friend) {
|
||||
|
|
@ -125,6 +127,7 @@
|
|||
$(btnFriendSelector, hoverSelector).hide();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.hideBubble = function() {
|
||||
|
|
|
|||
|
|
@ -201,9 +201,9 @@
|
|||
description: '',
|
||||
actions: [{ name: 'Signup', link: signupUrl }]
|
||||
};
|
||||
console.log("facebook feed options:", obj);
|
||||
logger.debug("facebook feed options:", obj);
|
||||
function fbFeedDialogCallback(response) {
|
||||
//console.log("feedback dialog closed: " + response['post_id'])
|
||||
//logger.debug("feedback dialog closed: " + response['post_id'])
|
||||
if (response && response['post_id']) {
|
||||
context.JK.GA.trackServiceInvitations(context.JK.GA.InvitationTypes.facebook, 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -570,7 +570,7 @@
|
|||
|
||||
/** check if the server is alive */
|
||||
function serverHealthCheck(options) {
|
||||
console.log("serverHealthCheck")
|
||||
logger.debug("serverHealthCheck")
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: "/api/versioncheck"
|
||||
|
|
@ -901,6 +901,27 @@
|
|||
});
|
||||
}
|
||||
|
||||
function createTextMessage(options) {
|
||||
var id = getId(options);
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: '/api/users/' + id + '/notifications',
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
});
|
||||
}
|
||||
|
||||
function getNotifications(options) {
|
||||
var id = getId(options);
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: '/api/users/' + id + '/notifications?' + $.param(options),
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
return self;
|
||||
}
|
||||
|
|
@ -979,6 +1000,8 @@
|
|||
this.getShareRecording = getShareRecording;
|
||||
this.tweet = tweet;
|
||||
this.createFbInviteUrl = createFbInviteUrl;
|
||||
this.createTextMessage = createTextMessage;
|
||||
this.getNotifications = getNotifications;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
var JamKazam = context.JK.JamKazam = function () {
|
||||
var app;
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var heartbeatInterval = null;
|
||||
var heartbeatMS = null;
|
||||
var heartbeatMissedMS = 10000; // if 5 seconds go by and we haven't seen a heartbeat ack, get upset
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
var lastHeartbeatAckTime = null;
|
||||
var lastHeartbeatFound = false;
|
||||
var heartbeatAckCheckInterval = null;
|
||||
var userDeferred = null;
|
||||
|
||||
var opts = {
|
||||
inClient: true, // specify false if you want the app object but none of the client-oriented features
|
||||
|
|
@ -57,6 +59,10 @@
|
|||
}
|
||||
rules[target] = {route: '/' + targetUrl + '/:d?', method: target};
|
||||
routingContext[target] = fn;
|
||||
|
||||
// allow dialogs to take an optional argument
|
||||
rules[target+'opt'] = {route: '/' + targetUrl + '/:d?/d1:', method: target};
|
||||
routingContext[target + 'opt'] = fn;
|
||||
});
|
||||
routes.context(routingContext);
|
||||
for (rule in rules) if (rules.hasOwnProperty(rule)) routes.add(rules[rule]);
|
||||
|
|
@ -214,7 +220,7 @@
|
|||
var errorResponse = JSON.parse(jqXHR.responseText)["errors"];
|
||||
for (var key in errorResponse) {
|
||||
var errorsForKey = errorResponse[key];
|
||||
console.log("key: " + key);
|
||||
logger.debug("key: " + key);
|
||||
var prettyKey = context.JK.entityToPrintable[key];
|
||||
if (!prettyKey) {
|
||||
prettyKey = key;
|
||||
|
|
@ -296,7 +302,7 @@
|
|||
if (jqXHR.status == 422) {
|
||||
var errors = JSON.parse(jqXHR.responseText);
|
||||
var $errors = context.JK.format_all_errors(errors);
|
||||
console.log("Unprocessable entity sent from server:", errors)
|
||||
logger.debug("Unprocessable entity sent from server:", errors)
|
||||
this.notify({title: title, text: $errors, icon_url: "/assets/content/icon_alert_big.png"})
|
||||
}
|
||||
else {
|
||||
|
|
@ -311,7 +317,7 @@
|
|||
if(bodyIndex > -1) {
|
||||
text = text.substr(bodyIndex);
|
||||
}
|
||||
console.log("html", text);
|
||||
logger.debug("html", text);
|
||||
$('#server-error-dialog .error-contents').html(text);
|
||||
app.layout.showDialog('server-error-dialog')
|
||||
return false;
|
||||
|
|
@ -356,7 +362,7 @@
|
|||
context.RouteMap.parse(hash);
|
||||
}
|
||||
catch (e) {
|
||||
console.log("ignoring bogus screen name: %o", hash)
|
||||
logger.debug("ignoring bogus screen name: %o", hash)
|
||||
hash = null;
|
||||
}
|
||||
|
||||
|
|
@ -369,6 +375,10 @@
|
|||
context.location = url;
|
||||
}
|
||||
|
||||
// call .done/.fail on this to wait for safe user data
|
||||
this.user = function() {
|
||||
return userDeferred;
|
||||
}
|
||||
|
||||
this.unloadFunction = function () {
|
||||
logger.debug("window.unload function called.");
|
||||
|
|
@ -395,6 +405,8 @@
|
|||
events();
|
||||
this.layout.handleDialogState();
|
||||
|
||||
userDeferred = rest.getUserDetail();
|
||||
|
||||
if (opts.inClient) {
|
||||
registerLoginAck();
|
||||
registerHeartbeatAck();
|
||||
|
|
@ -404,6 +416,10 @@
|
|||
registerDownloadAvailable();
|
||||
context.JK.FaderHelpers.initialize();
|
||||
context.window.onunload = this.unloadFunction;
|
||||
|
||||
userDeferred.fail(function(jqXHR) {
|
||||
app.notify({title: "Unable to Load User", text: "You should reload the page."})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//= require jquery
|
||||
//= require jquery.queryparams
|
||||
//= require AAA_Log
|
||||
//= require AAC_underscore
|
||||
//= require globals
|
||||
//= require jamkazam
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@
|
|||
}
|
||||
|
||||
function closeDialog(dialog) {
|
||||
console.log("closing dialog: " + dialog);
|
||||
logger.debug("closing dialog: " + dialog);
|
||||
var $dialog = $('[layout-id="' + dialog + '"]');
|
||||
dialogEvent(dialog, 'beforeHide');
|
||||
var $overlay = $('.dialog-overlay');
|
||||
|
|
@ -468,7 +468,7 @@
|
|||
var screen = location.page.substring(1); // remove leading slash
|
||||
var accepted = screenEvent(currentScreen, 'beforeLeave', {screen:screen, hash: context.location.hash});
|
||||
if(accepted === false) {
|
||||
console.log("navigation to " + context.location.hash + " rejected by " + currentScreen);
|
||||
logger.debug("navigation to " + context.location.hash + " rejected by " + currentScreen);
|
||||
//resettingHash = true;
|
||||
// reset the hash to where it just was
|
||||
context.location.hash = currentHash;
|
||||
|
|
@ -497,6 +497,7 @@
|
|||
|
||||
logger.debug("Changing screen to " + currentScreen);
|
||||
|
||||
logger.debug("data: ", data);
|
||||
screenEvent(currentScreen, 'beforeShow', data);
|
||||
|
||||
// For now -- it seems we want it open always.
|
||||
|
|
@ -522,7 +523,7 @@
|
|||
|
||||
// Show any requested dialog
|
||||
if ("d" in data) {
|
||||
showDialog(data.d);
|
||||
showDialog(data.d, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -575,7 +576,7 @@
|
|||
}
|
||||
|
||||
function showDialog(dialog, options) {
|
||||
if (!dialogEvent(dialog, 'beforeShow')) {
|
||||
if (!dialogEvent(dialog, 'beforeShow', options)) {
|
||||
return;
|
||||
}
|
||||
var $overlay = $('.dialog-overlay')
|
||||
|
|
@ -593,7 +594,7 @@
|
|||
var $dialog = $('[layout-id="' + dialog + '"]');
|
||||
stackDialogs($dialog, $overlay);
|
||||
$dialog.show();
|
||||
dialogEvent(dialog, 'afterShow');
|
||||
dialogEvent(dialog, 'afterShow', options);
|
||||
}
|
||||
|
||||
function centerDialog(dialog) {
|
||||
|
|
@ -845,8 +846,8 @@
|
|||
return onHashChange(e, postFunction);
|
||||
}
|
||||
|
||||
this.showDialog = function (dialog) {
|
||||
showDialog(dialog);
|
||||
this.showDialog = function (dialog, options) {
|
||||
showDialog(dialog, options);
|
||||
};
|
||||
|
||||
this.close = function (evt) {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
});
|
||||
}
|
||||
else {
|
||||
console.log("workin fool: %o", working)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@
|
|||
|
||||
$playmodeButton.on('ifChecked', function(e) {
|
||||
var playmode = $(this).val();
|
||||
console.log("set new playmode", playmode);
|
||||
logger.debug("set new playmode", playmode);
|
||||
setPlaybackMode(playmode);
|
||||
});
|
||||
|
||||
|
|
@ -160,7 +160,7 @@
|
|||
}
|
||||
|
||||
// at the end of the play, the duration sets to 0, as does currentTime. but isPlaying does not reset to
|
||||
console.log("currentTimeMs, durationTimeMs", currentTimeMs, durationTimeMs);
|
||||
logger.debug("currentTimeMs, durationTimeMs", currentTimeMs, durationTimeMs);
|
||||
if(currentTimeMs == 0 && durationTimeMs == 0) {
|
||||
if(isPlaying) {
|
||||
isPlaying = false;
|
||||
|
|
@ -168,7 +168,7 @@
|
|||
currentTimeMs = playbackDurationMs;
|
||||
stopPlay();
|
||||
endReached = true;
|
||||
console.log("end reached");
|
||||
logger.debug("end reached");
|
||||
}
|
||||
else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
var decrementedFriendCountOnce = false;
|
||||
var sentFriendRequest = false;
|
||||
var profileScreen = null;
|
||||
var textMessageDialog = null;
|
||||
|
||||
var instrument_logo_map = context.JK.getInstrumentIconMap24();
|
||||
|
||||
|
|
@ -110,6 +111,7 @@
|
|||
$('#btn-profile-edit').show();
|
||||
$('#btn-add-friend').hide();
|
||||
$('#btn-follow-user').hide();
|
||||
$('#btn-message-user').hide();
|
||||
}
|
||||
else {
|
||||
configureFriendFollowersControls();
|
||||
|
|
@ -117,6 +119,7 @@
|
|||
$('#btn-profile-edit').hide();
|
||||
$('#btn-add-friend').show();
|
||||
$('#btn-follow-user').show();
|
||||
$('#btn-message-user').show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,6 +154,7 @@
|
|||
// this doesn't need deferred because it's only shown when valid
|
||||
$('#btn-add-friend').click(handleFriendChange);
|
||||
$('#btn-follow-user').click(handleFollowingChange);
|
||||
$('#btn-message-user').click(handleMessageMusician);
|
||||
}
|
||||
|
||||
function handleFriendChange(evt) {
|
||||
|
|
@ -160,6 +164,7 @@
|
|||
else {
|
||||
sendFriendRequest(evt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleFollowingChange(evt) {
|
||||
|
|
@ -169,6 +174,12 @@
|
|||
else {
|
||||
addFollowing();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleMessageMusician(evt) {
|
||||
app.layout.showDialog('text-message', { d1: userId });
|
||||
return false;
|
||||
}
|
||||
|
||||
function sendFriendRequest(evt) {
|
||||
|
|
@ -741,7 +752,8 @@
|
|||
function bindFavorites() {
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
function initialize(textMessageDialogInstance) {
|
||||
textMessageDialog = textMessageDialogInstance;
|
||||
var screenBindings = {
|
||||
'beforeShow': beforeShow,
|
||||
'afterShow': afterShow
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@
|
|||
|
||||
timeCallback();
|
||||
|
||||
console.log("alert callback", type, text);
|
||||
logger.debug("alert callback", type, text);
|
||||
|
||||
if (type === 2) { // BACKEND_MIXER_CHANGE
|
||||
logger.debug("BACKEND_MIXER_CHANGE alert. reason:" + text);
|
||||
|
|
@ -904,7 +904,7 @@
|
|||
lookingForMixers[track.id] = participant.client_id;
|
||||
trackData.noaudio = true;
|
||||
if (!(lookingForMixersTimer)) {
|
||||
console.log("waiting for mixer to show up for track: " + track.id)
|
||||
logger.debug("waiting for mixer to show up for track: " + track.id)
|
||||
lookingForMixersTimer = context.setInterval(lookForMixers, 500);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@
|
|||
var jsFunction = "JK.Callbacks.clientPingResponse";
|
||||
var timeoutFunction = "JK.Callbacks.clientPingTimeout";
|
||||
|
||||
console.log("jamClient.TestLatency")
|
||||
console.time('jamClient.TestLatency');
|
||||
logger.debug("jamClient.TestLatency")
|
||||
//console.time('jamClient.TestLatency');
|
||||
jamClient.TestLatency(clientID, jsFunction, timeoutFunction);
|
||||
console.timeEnd('jamClient.TestLatency');
|
||||
//console.timeEnd('jamClient.TestLatency');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@
|
|||
})
|
||||
}
|
||||
else {
|
||||
console.log("no copy-to-clipboard capabilities")
|
||||
logger.debug("no copy-to-clipboard capabilities")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
var friends = [];
|
||||
var rest = context.JK.Rest();
|
||||
var invitationDialog = null;
|
||||
var textMessageDialog = null;
|
||||
|
||||
function initializeSearchPanel() {
|
||||
$('#search_text_type').change(function() {
|
||||
|
|
@ -139,13 +140,17 @@
|
|||
|
||||
$.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);
|
||||
}
|
||||
|
||||
// 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,
|
||||
text: val.formatted_msg.html(),
|
||||
date: $.timeago(val.created_at)
|
||||
});
|
||||
|
||||
|
|
@ -263,6 +268,30 @@
|
|||
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) {
|
||||
|
|
@ -350,8 +379,13 @@
|
|||
|
||||
// default handler for incoming notification
|
||||
function handleNotification(payload, type) {
|
||||
var sidebarText;
|
||||
sidebarText = payload.msg;
|
||||
|
||||
// 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();
|
||||
|
|
@ -359,16 +393,18 @@
|
|||
// add notification to sidebar
|
||||
var template = $("#template-notification-panel").html();
|
||||
var notificationHtml = context.JK.fillTemplate(template, {
|
||||
notificationId: payload.notification_id,
|
||||
notificationId: payload.notification_id,
|
||||
sessionId: payload.session_id,
|
||||
avatar_url: context.JK.resolveAvatarUrl(payload.photo_url),
|
||||
text: sidebarText,
|
||||
text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg ,
|
||||
date: $.timeago(payload.created_at)
|
||||
});
|
||||
|
||||
$('#sidebar-notification-list').prepend(notificationHtml);
|
||||
|
||||
initializeActions(payload, type);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var delay = (function(){
|
||||
|
|
@ -461,6 +497,9 @@
|
|||
registerSourceUp();
|
||||
registerSourceDown();
|
||||
|
||||
// register text messages
|
||||
registerTextMessage();
|
||||
|
||||
// watch for Invite More Users events
|
||||
$('#sidebar-div .btn-email-invitation').click(function() {
|
||||
invitationDialog.showEmailDialog();
|
||||
|
|
@ -1000,9 +1039,19 @@
|
|||
args.band_id,
|
||||
args.band_invitation_id,
|
||||
true
|
||||
).done(function(response) {
|
||||
).done(function(response) {
|
||||
deleteNotification(args.notification_id); // delete notification corresponding to this friend request
|
||||
}).error(app.ajaxError);
|
||||
}).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() {
|
||||
|
|
@ -1124,13 +1173,14 @@
|
|||
});
|
||||
}
|
||||
|
||||
this.initialize = function(invitationDialogInstance) {
|
||||
this.initialize = function(invitationDialogInstance, textMessageDialogInstance) {
|
||||
events();
|
||||
initializeSearchPanel();
|
||||
initializeFriendsPanel();
|
||||
initializeChatPanel();
|
||||
initializeNotificationsPanel();
|
||||
invitationDialog = invitationDialogInstance;
|
||||
textMessageDialog = textMessageDialogInstance;
|
||||
};
|
||||
}
|
||||
})(window,jQuery);
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.TextMessageDialog = function(app) {
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var $dialog = null;
|
||||
var $previousMessages = null;
|
||||
var $previousMessagesScroller = null;
|
||||
var $sendTextMessage = null;
|
||||
var $form = null;
|
||||
var $textBox = null;
|
||||
var userLookup = null;
|
||||
var otherId = null;
|
||||
var offset = 0;
|
||||
var sendingMessage = false;
|
||||
var LIMIT = 20;
|
||||
var showing = false;
|
||||
var fullyInitialized = false;
|
||||
var user = null;
|
||||
var renderQueue = []; // to handle race condition between dialog showing and populated using REST, and messages coming in
|
||||
|
||||
var remainingCap = 400;
|
||||
|
||||
function reset() {
|
||||
fullyInitialized = false;
|
||||
renderQueue = [];
|
||||
sendingMessage = false;
|
||||
offset = 0;
|
||||
userLookup = {};
|
||||
$previousMessages.empty();
|
||||
$textBox.val('');
|
||||
$sendTextMessage.unbind('click');
|
||||
}
|
||||
|
||||
function buildParams() {
|
||||
return { type: 'TEXT_MESSAGE', receiver: otherId, offset: offset, limit: LIMIT};
|
||||
}
|
||||
|
||||
function buildMessage() {
|
||||
var message = {};
|
||||
|
||||
message['message'] = $textBox.val();
|
||||
message['receiver'] = otherId;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
function sendMessage(e) {
|
||||
|
||||
var msg = $textBox.val();
|
||||
if(!msg || msg == '') {
|
||||
// don't bother the server with empty messages
|
||||
return;
|
||||
}
|
||||
|
||||
if(!sendingMessage) {
|
||||
sendingMessage = true;
|
||||
|
||||
$sendTextMessage.text('SENDING...')
|
||||
|
||||
rest.createTextMessage(buildMessage())
|
||||
.done(function() {
|
||||
$textBox.val('');
|
||||
renderMessage(msg, user.id, user.name, new Date().toISOString(), true);
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
app.notifyServerError(jqXHR, 'Unable to Send Message');
|
||||
})
|
||||
.always(function() {
|
||||
sendingMessage = false;
|
||||
$sendTextMessage.text('SEND');
|
||||
})
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function scrollToBottom(instant) {
|
||||
$previousMessagesScroller.animate({scrollTop: $previousMessagesScroller[0].scrollHeight}, instant ? 0 : 'slow');
|
||||
}
|
||||
|
||||
function renderMessage(msg, senderId, senderName, sent, append) {
|
||||
var options = {
|
||||
msg: msg,
|
||||
sender: senderId == user.id ? 'me' : senderName,
|
||||
sent: sent
|
||||
};
|
||||
var txt = $(context._.template($('#template-previous-message').html(), options, { variable: 'data' }));
|
||||
txt.find('.timeago').timeago();
|
||||
if(append) {
|
||||
$previousMessages.append(txt);
|
||||
scrollToBottom();
|
||||
}
|
||||
else {
|
||||
$previousMessages.prepend(txt);
|
||||
}
|
||||
}
|
||||
|
||||
function drainQueue() {
|
||||
context._.each(renderQueue, function(msg) {
|
||||
renderMessage(msg.msg, msg.senderId, msg.senderName, msg.sent, true);
|
||||
});
|
||||
renderQueue = [];
|
||||
}
|
||||
|
||||
function formatTextMessage(msg, sender_id, sender_name, clipped_msg) {
|
||||
var markedUpMsg = $('<span><span class="sender-name"></span> says: <span class="text-message"></span><a href="#" class="more-text-available"></a></span>');
|
||||
markedUpMsg.find('.sender-name').text(sender_name)
|
||||
markedUpMsg.find('.text-message').text(clipped_msg ? msg + "... " : msg).attr('data-is-clipped', clipped_msg)
|
||||
var moreTextLink = markedUpMsg.find('.more-text-available').attr('data-sender-id', sender_id);
|
||||
|
||||
if(clipped_msg) {
|
||||
moreTextLink.text('more').show();
|
||||
moreTextLink.click(function(e) {
|
||||
app.layout.showDialog('text-message', {d1: $(this).attr('data-sender-id')});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
moreTextLink.hide();
|
||||
}
|
||||
return markedUpMsg;
|
||||
}
|
||||
|
||||
function beforeShow(args) {
|
||||
|
||||
app.user()
|
||||
.done(function(userDetail) {
|
||||
user = userDetail;
|
||||
|
||||
var other = args.d1;
|
||||
|
||||
if(!other) throw "other must be specified in TextMessageDialog"
|
||||
otherId = other;
|
||||
|
||||
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
|
||||
|
||||
showing = true;
|
||||
|
||||
userLookup[user.id] = user;
|
||||
|
||||
rest.getUserDetail({id: otherId})
|
||||
.done(function(otherUser) {
|
||||
|
||||
userLookup[otherUser.id] = otherUser;
|
||||
$dialog.find('.receiver-name').text(otherUser.name);
|
||||
$dialog.find('textarea').attr('placeholder', 'enter a message to ' + otherUser.name + '...');
|
||||
$dialog.find('.offline-tip').text('An email will be sent if ' + otherUser.name + ' is offline');
|
||||
$sendTextMessage.click(sendMessage);
|
||||
|
||||
rest.getNotifications(buildParams())
|
||||
.done(function(response) {
|
||||
context._.each(response, function(textMessage) {
|
||||
renderMessage(textMessage.message, textMessage.source_user_id, userLookup[textMessage.source_user_id].name, textMessage.created_at);
|
||||
})
|
||||
|
||||
scrollToBottom(true);
|
||||
fullyInitialized = true;
|
||||
drainQueue();
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
app.notifyServerError(jqXHR, 'Unable to Load Conversation')
|
||||
})
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
app.notifyServerError(jqXHR, 'Unable to Load Other User')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
showing = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
function postMessage(e) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function events() {
|
||||
$form.submit(postMessage)
|
||||
}
|
||||
|
||||
|
||||
function respondTextInvitation(args) {
|
||||
app.layout.showDialog('text-message', {d1: args.sender_id}) ;
|
||||
}
|
||||
|
||||
// called from sidebar when messages come in
|
||||
function messageReceived(payload) {
|
||||
if(showing && otherId == payload.sender_id) {
|
||||
if(fullyInitialized) {
|
||||
renderMessage(payload.msg, payload.sender_id, payload.sender_name, payload.created_at, true);
|
||||
}
|
||||
else {
|
||||
// the dialog caught a message as it was initializing... queue it for later once dialog is showing
|
||||
renderQueue.push({msg: payload.msg, senderId: payload.sender_id, senderName: payload.sender_name, sent: msg.created_at});
|
||||
}
|
||||
}
|
||||
else {
|
||||
payload.msg = formatTextMessage(payload.msg, payload.sender_id, payload.sender_name, payload.clipped_msg);
|
||||
|
||||
app.notify({
|
||||
"title": "Message from " + payload.sender_name,
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "REPLY",
|
||||
"ok_callback": respondTextInvitation,
|
||||
"ok_callback_args": {
|
||||
"sender_id": payload.sender_id,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
function showDialog(_other) {
|
||||
|
||||
app.layout.closeDialog('text-message') // this dialog is implemented as a singleton, so must enforce this
|
||||
|
||||
reset();
|
||||
|
||||
if(!_other) throw "other must be specified in TextMessageDialog"
|
||||
otherId = _other;
|
||||
|
||||
app.layout.showDialog('text-message')
|
||||
}*/
|
||||
|
||||
function initialize() {
|
||||
var dialogBindings = {
|
||||
'beforeShow' : beforeShow,
|
||||
'afterHide': afterHide
|
||||
};
|
||||
|
||||
|
||||
app.bindDialog('text-message', dialogBindings);
|
||||
|
||||
$dialog = $('#text-message-dialog');
|
||||
$previousMessagesScroller = $dialog.find('.previous-messages-scroller');
|
||||
$previousMessages = $dialog.find('.previous-messages');
|
||||
$sendTextMessage = $dialog.find('.btn-send-text-message');
|
||||
$form = $dialog.find('form');
|
||||
$textBox = $form.find('textarea');
|
||||
|
||||
events();
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
this.messageReceived = messageReceived;
|
||||
this.formatTextMessage = formatTextMessage;
|
||||
}
|
||||
|
||||
return this;
|
||||
})(window,jQuery);
|
||||
|
|
@ -62,15 +62,14 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/api/users/' + context.JK.currentUserId
|
||||
}).done(function (r) {
|
||||
userMe = r;
|
||||
// TODO - Setting global variable for local user.
|
||||
context.JK.userMe = r;
|
||||
updateHeader();
|
||||
handleWhatsNext(userMe);
|
||||
}).fail(app.ajaxError);
|
||||
app.user()
|
||||
.done(function(r) {
|
||||
userMe = r;
|
||||
// TODO - Setting global variable for local user.
|
||||
context.JK.userMe = r;
|
||||
updateHeader();
|
||||
handleWhatsNext(userMe);
|
||||
});
|
||||
}
|
||||
|
||||
function updateHeader() {
|
||||
|
|
|
|||
|
|
@ -640,13 +640,13 @@
|
|||
|
||||
context.JK.hasOneConfiguredDevice = function() {
|
||||
var result = context.jamClient.FTUEGetGoodConfigurationList();
|
||||
console.log("hasOneConfiguredDevice: ", result);
|
||||
logger.debug("hasOneConfiguredDevice: ", result);
|
||||
return result.length > 0;
|
||||
};
|
||||
|
||||
context.JK.getGoodAudioConfigs = function() {
|
||||
var result = context.jamClient.FTUEGetGoodAudioConfigurations();
|
||||
console.log("goodAudioConfigs=%o", result);
|
||||
logger.debug("goodAudioConfigs=%o", result);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
@ -671,7 +671,7 @@
|
|||
badAudioConfigs.push(allAudioConfigs[i]);
|
||||
}
|
||||
}
|
||||
console.log("badAudioConfigs=%o", badAudioConfigs);
|
||||
logger.debug("badAudioConfigs=%o", badAudioConfigs);
|
||||
return badAudioConfigs;
|
||||
};
|
||||
|
||||
|
|
@ -718,6 +718,12 @@
|
|||
return deviceId;
|
||||
}
|
||||
|
||||
// returns /client#/home for http://www.jamkazam.com/client#/home
|
||||
context.JK.locationPath = function() {
|
||||
var bits = context.location.href.split('/');
|
||||
return '/' + bits.slice(3).join('/');
|
||||
}
|
||||
|
||||
context.JK.nowUTC = function() {
|
||||
var d = new Date();
|
||||
return new Date( d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() );
|
||||
|
|
|
|||
|
|
@ -25,7 +25,16 @@
|
|||
rest.login({email: email, password: password, remember_me: rememberMe})
|
||||
.done(function() {
|
||||
app.layout.closeDialog('signin-dialog')
|
||||
window.location = '/client'
|
||||
|
||||
var redirectTo = $.QueryString['redirect-to'];
|
||||
if(redirectTo) {
|
||||
logger.debug("redirectTo:" + redirectTo);
|
||||
window.location.href = redirectTo;
|
||||
}
|
||||
else {
|
||||
logger.debug("default post-login path");
|
||||
window.location.href = '/client'
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status == 422) {
|
||||
|
|
@ -62,6 +71,7 @@
|
|||
}
|
||||
|
||||
function beforeShow() {
|
||||
logger.debug("showing login form")
|
||||
reset();
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +80,7 @@
|
|||
}
|
||||
|
||||
function afterHide() {
|
||||
|
||||
logger.debug("hiding login form")
|
||||
}
|
||||
|
||||
function initialize(){
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
}
|
||||
else {
|
||||
var videoUrl = $.param.querystring(window.location.href, 'showVideo=' + encodeURIComponent($self.data('video-url')));
|
||||
console.log("videoUrl: ", videoUrl);
|
||||
context.jamClient.OpenSystemBrowser(videoUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@
|
|||
//= require web/videoDialog
|
||||
//= require invitationDialog
|
||||
//= require tickDuration
|
||||
//= require hoverMusician
|
||||
//= require feed_item_recording
|
||||
//= require feed_item_session
|
||||
//= require hoverMusician
|
||||
//= require hoverFan
|
||||
//= require hoverBand
|
||||
//= require hoverSession
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
var welcomeRoot;
|
||||
var rest = context.JK.Rest();
|
||||
var logger = context.JK.logger;
|
||||
|
||||
function initialize() {
|
||||
|
||||
|
|
@ -22,7 +23,15 @@
|
|||
rest.getUserDetail({id:context.JK.currentUserId})
|
||||
.done(function () {
|
||||
e.preventDefault();
|
||||
window.location = '/client';
|
||||
var redirectTo = $.QueryString['redirect-to'];
|
||||
if(redirectTo) {
|
||||
logger.debug("redirectTo:" + redirectTo);
|
||||
window.location = redirectTo;
|
||||
}
|
||||
else {
|
||||
logger.debug("default post-login path");
|
||||
window.location = '/client';
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
context.JK.app.layout.showDialog('signin-dialog');
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
*= require ./localRecordingsDialog
|
||||
*= require ./serverErrorDialog
|
||||
*= require ./leaveSessionWarning
|
||||
*= require ./textMessageDialog
|
||||
*= require ./terms
|
||||
*= require ./createSession
|
||||
*= require ./feed
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
z-index:999;
|
||||
}
|
||||
|
||||
.bubble.musician-bubble {
|
||||
width:400px;
|
||||
}
|
||||
|
||||
.bubble h2 {
|
||||
padding:6px 0px;
|
||||
text-align:center;
|
||||
|
|
|
|||
|
|
@ -526,6 +526,26 @@ hr {
|
|||
margin: 10px 0;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
font-family: Raleway, Arial, Helvetica, sans-serif;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
:-moz-placeholder { /* Firefox 18- */
|
||||
font-family: Raleway, Arial, Helvetica, sans-serif;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
::-moz-placeholder { /* Firefox 19+ */
|
||||
font-family: Raleway, Arial, Helvetica, sans-serif;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
font-family: Raleway, Arial, Helvetica, sans-serif;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
// infinitescroll required element
|
||||
.btn-next-pager {
|
||||
display:none;
|
||||
|
|
|
|||
|
|
@ -31,3 +31,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
#notification p .text-message {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -261,5 +261,15 @@
|
|||
margin-left:-3px;
|
||||
}
|
||||
}
|
||||
|
||||
#sidebar-notification-list {
|
||||
|
||||
.text-message {
|
||||
word-wrap:break-word;
|
||||
}
|
||||
|
||||
.more-text-available {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
#text-message-dialog {
|
||||
width:500px;
|
||||
|
||||
.previous-message-sender {
|
||||
font-weight:bold;
|
||||
margin-right:10px;
|
||||
margin-left:-10px;
|
||||
color:#ED3618;
|
||||
|
||||
&:after {
|
||||
content:':'
|
||||
}
|
||||
}
|
||||
.previous-messages-scroller {
|
||||
height:250px;
|
||||
position:relative;
|
||||
display:block;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
.previous-messages {
|
||||
padding:10px;
|
||||
color:white;
|
||||
}
|
||||
|
||||
.previous-message {
|
||||
margin:5px 0;
|
||||
}
|
||||
|
||||
.previous-message-text {
|
||||
line-height:18px;
|
||||
}
|
||||
|
||||
.previous-message-timestamp {
|
||||
margin-top:4px;
|
||||
color:#AAA;
|
||||
display:block;
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
.text-message-scroller {
|
||||
width: 100%;
|
||||
height: 4em;
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.text-message-box {
|
||||
|
||||
}
|
||||
|
||||
#new-text-message {
|
||||
width:100%;
|
||||
height:40px;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ class ApiController < ApplicationController
|
|||
end
|
||||
rescue_from 'JamRuby::JamArgumentError' do |exception|
|
||||
@exception = exception
|
||||
render "errors/jam_argument_error", :status => 400
|
||||
render "errors/jam_argument_error", :status => 422
|
||||
end
|
||||
rescue_from 'JamRuby::PermissionError' do |exception|
|
||||
@exception = exception
|
||||
|
|
|
|||
|
|
@ -302,7 +302,24 @@ class ApiUsersController < ApiController
|
|||
|
||||
###################### NOTIFICATIONS ####################
|
||||
def notification_index
|
||||
@notifications = @user.notifications
|
||||
if params[:type] == 'TEXT_MESSAGE'
|
||||
# you can ask for just text_message notifications
|
||||
|
||||
raise JamArgumentError.new('can\'t be blank', 'receiver') if params[:receiver].blank?
|
||||
raise JamArgumentError.new('can\'t be blank', 'limit') if params[:limit].blank?
|
||||
raise JamArgumentError.new('can\'t be blank', 'offset') if params[:offset].blank?
|
||||
|
||||
receiver_id = params[:receiver]
|
||||
limit = params[:limit]
|
||||
limit = limit.to_i
|
||||
offset = params[:offset]
|
||||
offset = offset.to_i
|
||||
@notifications = Notification.where(description: 'TEXT_MESSAGE').where('(source_user_id = (?) AND target_user_id = (?)) OR (source_user_id = (?) AND target_user_id = (?))', @user.id, receiver_id, receiver_id, @user.id).offset(offset).limit(limit).order('created_at DESC')
|
||||
else
|
||||
# you can ask for all notifications
|
||||
@notifications = @user.notifications
|
||||
end
|
||||
|
||||
respond_with @notifications, responder: ApiResponder, :status => 200
|
||||
end
|
||||
|
||||
|
|
@ -311,6 +328,12 @@ class ApiUsersController < ApiController
|
|||
respond_with responder: ApiResponder, :status => 204
|
||||
end
|
||||
|
||||
def notification_create
|
||||
@notification = Notification.send_text_message(params[:message], current_user, User.find_by_id(params[:receiver]))
|
||||
respond_with_model(@notification, new: true)
|
||||
end
|
||||
|
||||
|
||||
##################### BAND INVITATIONS ##################
|
||||
def band_invitation_index
|
||||
@invitations = @user.received_band_invitations
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ class ClientsController < ApplicationController
|
|||
gon.use_cached_session_scores = Rails.application.config.use_cached_session_scores
|
||||
gon.allow_both_find_algos = Rails.application.config.allow_both_find_algos
|
||||
|
||||
if current_user
|
||||
#if current_user
|
||||
render :layout => 'client'
|
||||
else
|
||||
redirect_to root_url
|
||||
end
|
||||
#else
|
||||
# redirect_to root_url
|
||||
#end
|
||||
end
|
||||
|
||||
AUTHED = %W{friend}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,20 @@ module SessionsHelper
|
|||
def set_remember_token(user)
|
||||
if @session_only_cookie
|
||||
cookies.delete(:remember_token)
|
||||
cookies[:remember_token] = user.remember_token
|
||||
cookies[:remember_token] = {
|
||||
value: user.remember_token,
|
||||
domain: Rails.application.config.session_cookie_domain
|
||||
}
|
||||
else
|
||||
cookies[:remember_token] = {
|
||||
:value => user.remember_token,
|
||||
:expires => 20.years.from_now.utc
|
||||
value: user.remember_token,
|
||||
expires: 20.years.from_now.utc,
|
||||
domain: Rails.application.config.session_cookie_domain
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def signed_in?
|
||||
!current_user.nil?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
object @notification
|
||||
|
||||
attributes :id
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
collection @notifications
|
||||
|
||||
attributes :description, :source_user_id, :target_user_id, :session_id, :recording_id, :invitation_id, :join_request_id, :friend_request_id, :band_id, :band_invitation_id, :formatted_msg, :created_at
|
||||
attributes :description, :source_user_id, :target_user_id, :session_id, :recording_id, :invitation_id, :join_request_id, :friend_request_id, :band_id, :band_invitation_id, :formatted_msg, :message, :created_at
|
||||
|
||||
node :source_user do |n|
|
||||
source_user_data = {}
|
||||
if n.source_user
|
||||
source_user_data[:name] = n.source_user.name
|
||||
end
|
||||
source_user_data
|
||||
end
|
||||
|
||||
node :notification_id do |n|
|
||||
n.id
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div id="band-hover" class="hidden bubble">
|
||||
<div id="band-hover" class="hidden bubble band-bubble">
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div id="fan-hover" class="hidden bubble">
|
||||
<div id="fan-hover" class="hidden bubble fan-bubble">
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div id="musician-hover" class="hidden bubble">
|
||||
<div id="musician-hover" class="hidden bubble musician-bubble">
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -146,6 +146,23 @@
|
|||
});
|
||||
}
|
||||
|
||||
function messageMusician(userId) {
|
||||
if(JK.TextMessageDialogInstance) {
|
||||
JK.app.layout.showDialog('text-message', {d1: userId})
|
||||
}
|
||||
else {
|
||||
var goto = $('<span>We are sorry, but you can\'t message from this page.</span>');
|
||||
JK.app.notify(
|
||||
{
|
||||
title: "Unable to Message From Here",
|
||||
text: goto
|
||||
},
|
||||
{ no_cancel: true });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function adjustMusicianFollowingCount(value) {
|
||||
$("#spnFollowCount", "#musician-hover").html(parseInt($("#spnFollowCount", "#musician-hover").html()) + value);
|
||||
}
|
||||
|
|
@ -200,6 +217,7 @@
|
|||
<div class="left" style="display:none;"><a id="btnLike" onclick="addLike('{userId}');" class="button-orange">LIKE</a></div>
|
||||
<div class="left"><a id="btnFriend" onclick="{friendAction}('{userId}');" class="button-orange">CONNECT</a></div>
|
||||
<div class="left"><a id="btnFollow" onclick="{followAction}('{userId}');" class="button-orange">FOLLOW</a></div>
|
||||
<div class="left"><a id="btnMessage" onclick="messageMusician('{userId}');" class="button-orange">MESSAGE</a></div>
|
||||
</div>
|
||||
<br /><br />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<%= select_tag('musician_query_distance', options_for_select(Search::M_DISTANCE_OPTS, Search::M_MILES_DEFAULT)) %>
|
||||
<% end -%>
|
||||
<%= content_tag(:div, :class => 'filter-element') do -%>
|
||||
miles of <%= content_tag(:span, current_user.current_city(request.remote_ip), :id => 'musician-filter-city') %>
|
||||
miles of <%= content_tag(:span, current_user ? current_user.current_city(request.remote_ip) : '', :id => 'musician-filter-city') %>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<%= content_tag(:div,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@
|
|||
<%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_MUSICIAN}) %>
|
||||
<%= content_tag(:div, :class => 'filter-body') do %>
|
||||
<%= content_tag(:div, :class => 'content-body-scroller') do -%>
|
||||
<%= content_tag(:div, content_tag(:div, '', :id => 'musician-filter-results', :class => 'filter-results'), :class => 'content-wrapper musician-wrapper') %>
|
||||
<%= content_tag(:div, :class => 'content-wrapper musician-wrapper') do -%>
|
||||
<%= content_tag(:div, '', :id => 'musician-filter-results', :class => 'filter-results') %>
|
||||
<%= content_tag(:div, 'No more results.', :class => 'end-of-list', :id => 'end-of-musician-list') %>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
|
|
@ -63,10 +66,11 @@
|
|||
|
||||
<script type="text/template" id="template-musician-action-btns">
|
||||
<a href="{profile_url}" class="button-orange smallbutton">PROFILE</a>
|
||||
<% if current_user.musician? %>
|
||||
<% if current_user && current_user.musician? %>
|
||||
<a href="#" class="{friend_class} smallbutton search-m-friend">{friend_caption}</a>
|
||||
<% end %>
|
||||
<a href="#" class="{follow_class} smallbutton search-m-follow">{follow_caption}</a>
|
||||
<a href="#" class="{message_class} smallbutton search-m-message">{message_caption}</a>
|
||||
<!--<a href="#" class="{button_message} smallbutton search-m-like">MESSAGE</a>-->
|
||||
<div class="clearall"></div>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<div class="right">
|
||||
<a id="btn-add-friend" class="button-orange">ADD FRIEND</a>
|
||||
<a id="btn-follow-user" class="button-orange">FOLLOW</a>
|
||||
<a id="btn-message-user" class="button-orange">MESSAGE</a>
|
||||
<%= link_to("EDIT PROFILE", '/client#/account/profile', :id => "btn-profile-edit", :class => "button-orange") %>
|
||||
</div>
|
||||
<br clear="all" /><br />
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@
|
|||
</li>
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Chat panel template -->
|
||||
<script type="text/template" id="template-chat-panel">
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
.dialog.textMessage-overlay.ftue-overlay.tall{ layout: 'dialog', 'layout-id' => 'text-message', id: 'text-message-dialog'}
|
||||
.content-head
|
||||
= image_tag "content/icon_comment.png", {:width => 12, :height => 12, :class => 'content-icon' }
|
||||
%h1
|
||||
= 'conversation with'
|
||||
%span.receiver-name
|
||||
.dialog-inner
|
||||
.previous-messages-scroller
|
||||
.previous-messages
|
||||
.text-message-scroller
|
||||
%form.text-message-box
|
||||
%textarea{ name: 'new-text-message', id: 'new-text-message' }
|
||||
.left
|
||||
%span.small.offline-tip An email will be sent if recipient is offline
|
||||
.right
|
||||
%a.button-grey.btn-close-dialog{href:'#', 'layout-action' => 'close'} CLOSE
|
||||
%a.button-orange.btn-send-text-message{href:'#'} SEND
|
||||
|
||||
%script{type: 'text/template', id: 'template-previous-message'}
|
||||
.previous-message
|
||||
%span.previous-message-sender= '{{data.sender}}'
|
||||
%span.previous-message-text= '{{data.msg}}'
|
||||
%time.previous-message-timestamp.timeago{datetime: '{{data.sent}}'}= '{{data.sent}}'
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
<%= select_tag("#{filter_label}_query_distance", options_for_select(Search::DISTANCE_OPTS, default_distance), {:class => 'easydropdown'}) %>
|
||||
<% end -%>
|
||||
<%= content_tag(:div, :class => 'filter-element desc') do -%>
|
||||
miles of <%= content_tag(:span, current_user.current_city(request.remote_ip), :id => "#{filter_label}-filter-city") %>
|
||||
miles of <%= content_tag(:span, current_user ? current_user.current_city(request.remote_ip) : '', :id => "#{filter_label}-filter-city") %>
|
||||
<% end -%>
|
||||
<!-- @end distance filter -->
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
<%= render "recordingFinishedDialog" %>
|
||||
<%= render "localRecordingsDialog" %>
|
||||
<%= render "showServerErrorDialog" %>
|
||||
<%= render "textMessageDialog" %>
|
||||
<%= render "notify" %>
|
||||
<%= render "client_update" %>
|
||||
<%= render "banner" %>
|
||||
|
|
@ -107,6 +108,11 @@
|
|||
JK.currentUserName = null;
|
||||
JK.currentUserMusician = null;
|
||||
JK.currentUserAdmin = false;
|
||||
|
||||
// you need to be logged in to use this part of the interface.
|
||||
// save original URL, and redirect to the home page
|
||||
logger.debug("redirecting back to / because not logged in")
|
||||
window.location.href = '/?redirect-to=' + encodeURIComponent(JK.locationPath());
|
||||
<% end %>
|
||||
|
||||
// Some things can't be initialized until we're connected. Put them here.
|
||||
|
|
@ -123,6 +129,10 @@
|
|||
var invitationDialog = new JK.InvitationDialog(JK.app);
|
||||
invitationDialog.initialize(facebookHelper);
|
||||
|
||||
var textMessageDialog = new JK.TextMessageDialog(JK.app);
|
||||
JK.TextMessageDialogInstance = textMessageDialog;
|
||||
textMessageDialog.initialize();
|
||||
|
||||
var localRecordingsDialog = new JK.LocalRecordingsDialog(JK.app);
|
||||
localRecordingsDialog.initialize();
|
||||
|
||||
|
|
@ -144,13 +154,13 @@
|
|||
header.initialize();
|
||||
|
||||
var sidebar = new JK.Sidebar(JK.app);
|
||||
sidebar.initialize(invitationDialog);
|
||||
sidebar.initialize(invitationDialog, textMessageDialog);
|
||||
|
||||
var homeScreen = new JK.HomeScreen(JK.app);
|
||||
homeScreen.initialize();
|
||||
|
||||
var profileScreen = new JK.ProfileScreen(JK.app);
|
||||
profileScreen.initialize();
|
||||
profileScreen.initialize(textMessageDialog);
|
||||
|
||||
var bandProfileScreen = new JK.BandProfileScreen(JK.app);
|
||||
bandProfileScreen.initialize();
|
||||
|
|
@ -197,7 +207,7 @@
|
|||
findSessionScreen.initialize(sessionLatency);
|
||||
|
||||
var findMusicianScreen = new JK.FindMusicianScreen(JK.app);
|
||||
findMusicianScreen.initialize();
|
||||
findMusicianScreen.initialize(textMessageDialog);
|
||||
|
||||
var findBandScreen = new JK.FindBandScreen(JK.app);
|
||||
findBandScreen.initialize();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
object @exception
|
||||
|
||||
attributes :message
|
||||
node do |exception|
|
||||
field = exception.field ? exception.field : 'unknown'
|
||||
errors = {}
|
||||
errors[field] = [exception.field_message]
|
||||
{
|
||||
errors: errors
|
||||
}
|
||||
end
|
||||
|
||||
node "type" do
|
||||
"ArgumentError"
|
||||
|
|
|
|||
|
|
@ -218,5 +218,7 @@ if defined?(Bundler)
|
|||
# should we use the new FindSessions API that has server-side scores
|
||||
config.use_cached_session_scores = true
|
||||
config.allow_both_find_algos = false
|
||||
|
||||
config.session_cookie_domain = nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
unless $rails_rake_task
|
||||
|
||||
JamWebEventMachine.start
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ SampleApp::Application.routes.draw do
|
|||
# notifications
|
||||
match '/users/:id/notifications' => 'api_users#notification_index', :via => :get
|
||||
match '/users/:id/notifications/:notification_id' => 'api_users#notification_destroy', :via => :delete
|
||||
match '/users/:id/notifications' => 'api_users#notification_create', :via => :post
|
||||
|
||||
# user band invitations
|
||||
match '/users/:id/band_invitations' => 'api_users#band_invitation_index', :via => :get
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ namespace :db do
|
|||
make_recording
|
||||
end
|
||||
|
||||
# takes command line args: http://davidlesches.com/blog/passing-arguments-to-a-rails-rake-task
|
||||
task :populate_conversation, [:target_email] => :environment do |task, args|
|
||||
populate_conversation(args.target_email)
|
||||
end
|
||||
|
||||
desc "Fill database with music session sample data"
|
||||
task populate_music_sessions: :environment do
|
||||
make_users(10) if 14 > User.count
|
||||
|
|
@ -272,4 +277,19 @@ def make_recording
|
|||
mix.completed = true
|
||||
recording.mixes << mix
|
||||
recording.save!(validate:false)
|
||||
end
|
||||
|
||||
def populate_conversation(target_email)
|
||||
all_users = User.all
|
||||
|
||||
target_users = target_email ? User.where(email: target_email) : all_users
|
||||
|
||||
target_users.each do |target_user|
|
||||
all_users.each do |other_user|
|
||||
next if target_user == other_user
|
||||
20.times do
|
||||
FactoryGirl.create(:notification_text_message, target_user: target_user, source_user: other_user, message: Faker::Lorem.characters(rand(400)))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -400,4 +400,12 @@ FactoryGirl.define do
|
|||
|
||||
factory :event_session, :class => JamRuby::EventSession do
|
||||
end
|
||||
|
||||
factory :notification, :class => JamRuby::Notification do
|
||||
|
||||
factory :notification_text_message do
|
||||
description 'TEXT_MESSAGE'
|
||||
message Faker::Lorem.characters(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Text Message", :js => true, :type => :feature, :capybara_feature => true do
|
||||
|
||||
before(:all) do
|
||||
User.delete_all # we delete all users due to the use of find_musician() helper method, which scrolls through all users
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@user1 = FactoryGirl.create(:user)
|
||||
@user2 = FactoryGirl.create(:user, first_name: 'bone_crusher')
|
||||
sign_in_poltergeist(@user1)
|
||||
end
|
||||
|
||||
describe "burn em up" do
|
||||
in_client "one" do
|
||||
|
||||
end
|
||||
|
||||
in_client "two" do
|
||||
|
||||
end
|
||||
end
|
||||
# what are all the ways to launch the dialog?
|
||||
describe "launches" do
|
||||
|
||||
it "on hover bubble" do
|
||||
site_search(@user2.first_name, expand: true)
|
||||
|
||||
find("#search-results a[user-id=\"#{@user2.id}\"][hoveraction=\"musician\"]", text: @user2.name).hover_intent
|
||||
find('#musician-hover #btnMessage').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
end
|
||||
|
||||
it "on find musician in musician's tile" do
|
||||
musician = find_musician(@user2)
|
||||
find(".result-list-button-wrapper[data-musician-id='#{@user2.id}'] .search-m-message").trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
end
|
||||
|
||||
it "on musician profile" do
|
||||
visit "/client#/profile/#{@user2.id}"
|
||||
find('#btn-message-user').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
end
|
||||
|
||||
it "on reply of notification in sidebar" do
|
||||
|
||||
# create a notification
|
||||
notification = Notification.send_text_message("bibbity bobbity boo", @user2, @user1)
|
||||
notification.errors.any?.should be_false
|
||||
|
||||
open_sidebar
|
||||
|
||||
# find the notification and click REPLY
|
||||
find("[layout-id='panelNotifications'] [notification-id='#{notification.id}'] .button-orange", text:'REPLY').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
end
|
||||
|
||||
it "on hit reply in message notification" do
|
||||
|
||||
in_client(@user1) do
|
||||
sign_in_poltergeist(@user1)
|
||||
end
|
||||
|
||||
in_client(@user2) do
|
||||
sign_in_poltergeist(@user2)
|
||||
|
||||
site_search(@user1.name, expand: true)
|
||||
|
||||
find("#search-results a[user-id=\"#{@user1.id}\"][hoveraction=\"musician\"]", text: @user1.name).hover_intent
|
||||
find('#musician-hover #btnMessage').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user1.name)
|
||||
|
||||
send_text_message("Hello to user id #{@user1.id}", close_on_send: true)
|
||||
end
|
||||
|
||||
in_client(@user1) do
|
||||
find('#notification #ok-button').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
end
|
||||
end
|
||||
|
||||
it "can load directly into chat session from url" do
|
||||
sign_in_poltergeist(@user1)
|
||||
visit "/"
|
||||
find('h1', text: 'Play music together over the Internet as if in the same room')
|
||||
visit "/client#/home/text-message/d1=#{@user2.id}"
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe "chat dialog behavior" do
|
||||
|
||||
it "send a message to someone" do
|
||||
in_client(@user1) do
|
||||
sign_in_poltergeist(@user1)
|
||||
end
|
||||
|
||||
in_client(@user2) do
|
||||
sign_in_poltergeist(@user2)
|
||||
|
||||
site_search(@user1.name, expand: true)
|
||||
|
||||
find("#search-results a[user-id=\"#{@user1.id}\"][hoveraction=\"musician\"]", text: @user1.name).hover_intent
|
||||
find('#musician-hover #btnMessage').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user1.name)
|
||||
|
||||
send_text_message("Oh hai to user id #{@user1.id}")
|
||||
end
|
||||
|
||||
in_client(@user1) do
|
||||
find('#notification #ok-button').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
find('.previous-message-text', text: "Oh hai to user id #{@user1.id}")
|
||||
send_text_message('hey there yourself')
|
||||
end
|
||||
|
||||
in_client(@user2) do
|
||||
find('.previous-message-text', text: "hey there yourself")
|
||||
send_text_message('ok bye', close_on_send: true)
|
||||
|
||||
end
|
||||
|
||||
in_client(@user1) do
|
||||
find('.previous-message-text', text: "ok bye")
|
||||
send_text_message('bye now', close_on_send: true)
|
||||
end
|
||||
end
|
||||
|
||||
it "shows error with a notify" do
|
||||
sign_in_poltergeist(@user1)
|
||||
visit '/'
|
||||
find('h1', text: 'Play music together over the Internet as if in the same room')
|
||||
visit "/client#/home/text-message/d1=#{@user2.id}"
|
||||
find('h1', text: 'conversation with ' + @user2.name)
|
||||
send_text_message('ass', should_fail:'profanity')
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -86,6 +86,37 @@ describe "Welcome", :js => true, :type => :feature, :capybara_feature => true d
|
|||
end
|
||||
end
|
||||
|
||||
describe "redirect-to" do
|
||||
|
||||
it "redirect on login" do
|
||||
visit "/client#/account"
|
||||
find('.curtain')
|
||||
find('h1', text: 'Play music together over the Internet as if in the same room')
|
||||
find('#signin').trigger(:click)
|
||||
within('#signin-form') do
|
||||
fill_in "email", with: user.email
|
||||
fill_in "password", with: user.password
|
||||
click_button "SIGN IN"
|
||||
end
|
||||
|
||||
wait_until_curtain_gone
|
||||
|
||||
find('h2', text: 'identity:')
|
||||
end
|
||||
|
||||
it "redirect if already logged in" do
|
||||
# this is a rare case
|
||||
sign_in_poltergeist(user)
|
||||
visit "/?redirect-to=" + ERB::Util.url_encode("/client#/account")
|
||||
find('h1', text: 'Play music together over the Internet as if in the same room')
|
||||
find('#signin').trigger(:click)
|
||||
|
||||
wait_until_curtain_gone
|
||||
|
||||
find('h2', text: 'identity:')
|
||||
end
|
||||
end
|
||||
|
||||
describe "signin with facebook" do
|
||||
|
||||
before(:each) do
|
||||
|
|
|
|||
|
|
@ -250,9 +250,9 @@ describe "Band API", :type => :api do
|
|||
it "should not allow user to create invitation to a Fan for band A" do
|
||||
recipient = FactoryGirl.create(:fan)
|
||||
last_response = create_band_invitation(band.id, recipient.id)
|
||||
last_response.status.should == 400
|
||||
last_response.status.should == 422
|
||||
error_msg = JSON.parse(last_response.body)
|
||||
error_msg["message"].should == BandInvitation::BAND_INVITATION_FAN_RECIPIENT_ERROR
|
||||
error_msg["errors"]['receiver'].should == [BandInvitation::BAND_INVITATION_FAN_RECIPIENT_ERROR]
|
||||
|
||||
# test receiver relationships
|
||||
recipient.received_band_invitations.size.should == 0
|
||||
|
|
|
|||
|
|
@ -983,7 +983,6 @@ describe "User API", :type => :api do
|
|||
let(:track) { FactoryGirl.create(:track, :connection => connection, :instrument => instrument) }
|
||||
let(:music_session) { ms = FactoryGirl.create(:music_session, :creator => user, :musician_access => true); ms.connections << connection; ms.save!; ms }
|
||||
|
||||
|
||||
it "fetches facebook successfully" do
|
||||
login(user.email, user.password, 200, true)
|
||||
get "/api/users/#{user.id}/share/session/facebook.json?music_session=#{music_session.id}", nil, "CONTENT_TYPE" => 'application/json'
|
||||
|
|
@ -997,6 +996,118 @@ describe "User API", :type => :api do
|
|||
end
|
||||
end
|
||||
|
||||
describe "notifications" do
|
||||
|
||||
let(:other) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
login(user.email, user.password, 200, true)
|
||||
end
|
||||
|
||||
it "create text notification" do
|
||||
post "/api/users/#{user.id}/notifications.json", {message: 'bibbity bobbity boo', receiver:other.id }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should == 201
|
||||
response = JSON.parse(last_response.body)
|
||||
response['id'].should_not be_nil
|
||||
|
||||
# verify that it can be found
|
||||
get "/api/users/#{user.id}/notifications.json", {type: 'TEXT_MESSAGE', receiver: other.id, limit:20, offset:0}, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should == 200
|
||||
response = JSON.parse(last_response.body)
|
||||
response.length.should == 1
|
||||
end
|
||||
|
||||
it "bad language causes 422" do
|
||||
post "/api/users/#{user.id}/notifications.json", {message: 'ass', receiver:other.id }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should == 422
|
||||
response = JSON.parse(last_response.body)
|
||||
response['errors']['message'].should == ['cannot contain profanity']
|
||||
end
|
||||
|
||||
it "bad receiver causes 422" do
|
||||
post "/api/users/#{user.id}/notifications.json", {message: 'ass' }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should == 422
|
||||
response = JSON.parse(last_response.body)
|
||||
response['errors']['target_user'].should == ['can\'t be blank']
|
||||
end
|
||||
|
||||
describe "index" do
|
||||
describe "text message index" do
|
||||
it "requires receiver id" do
|
||||
# verify that it can be found
|
||||
get "/api/users/#{user.id}/notifications.json", {type: 'TEXT_MESSAGE'}, "CONTENT_TYPE" => 'application/json'
|
||||
response = JSON.parse(last_response.body)
|
||||
response['errors']['receiver'].should == ['can\'t be blank']
|
||||
last_response.status.should == 422
|
||||
end
|
||||
|
||||
it "requires limit" do
|
||||
# verify that it can be found
|
||||
get "/api/users/#{user.id}/notifications.json", {type: 'TEXT_MESSAGE', receiver: other.id, offset:0}, "CONTENT_TYPE" => 'application/json'
|
||||
response = JSON.parse(last_response.body)
|
||||
response['errors']['limit'].should == ['can\'t be blank']
|
||||
last_response.status.should == 422
|
||||
end
|
||||
|
||||
it "requires offset" do
|
||||
# verify that it can be found
|
||||
get "/api/users/#{user.id}/notifications.json", {type: 'TEXT_MESSAGE', receiver: other.id, limit:20}, "CONTENT_TYPE" => 'application/json'
|
||||
response = JSON.parse(last_response.body)
|
||||
response['errors']['offset'].should == ['can\'t be blank']
|
||||
last_response.status.should == 422
|
||||
end
|
||||
|
||||
it "returns no results" do
|
||||
# verify that it can be found
|
||||
get "/api/users/#{user.id}/notifications.json", {type: 'TEXT_MESSAGE', receiver: other.id, offset:0, limit:20}, "CONTENT_TYPE" => 'application/json'
|
||||
response = JSON.parse(last_response.body)
|
||||
response.length.should == 0
|
||||
last_response.status.should == 200
|
||||
end
|
||||
|
||||
it "returns one results" do
|
||||
msg1 = FactoryGirl.create(:notification_text_message, source_user: user, target_user: other)
|
||||
# verify that it can be found
|
||||
get "/api/users/#{user.id}/notifications.json", {type: 'TEXT_MESSAGE', receiver: other.id, offset:0, limit:20}, "CONTENT_TYPE" => 'application/json'
|
||||
response = JSON.parse(last_response.body)
|
||||
response.length.should == 1
|
||||
response[0]['notification_id'].should == msg1.id
|
||||
response[0]['description'].should == msg1.description
|
||||
response[0]['message'].should == msg1.message
|
||||
response[0]['source_user_id'].should == msg1.source_user_id
|
||||
response[0]['target_user_id'].should == msg1.target_user_id
|
||||
last_response.status.should == 200
|
||||
login(other.email, other.password, 200, true)
|
||||
get "/api/users/#{other.id}/notifications.json", {type: 'TEXT_MESSAGE', receiver: user.id, offset:0, limit:20}, "CONTENT_TYPE" => 'application/json'
|
||||
response = JSON.parse(last_response.body)
|
||||
response.length.should == 1
|
||||
response[0]['notification_id'].should == msg1.id
|
||||
response[0]['description'].should == msg1.description
|
||||
response[0]['message'].should == msg1.message
|
||||
response[0]['source_user_id'].should == msg1.source_user_id
|
||||
response[0]['target_user_id'].should == msg1.target_user_id
|
||||
last_response.status.should == 200
|
||||
end
|
||||
|
||||
it "returns sorted results" do
|
||||
msg1 = FactoryGirl.create(:notification_text_message, source_user: user, target_user: other)
|
||||
msg2 = FactoryGirl.create(:notification_text_message, source_user: user, target_user: other, created_at: 1.days.ago)
|
||||
# verify that it can be found
|
||||
get "/api/users/#{user.id}/notifications.json", {type: 'TEXT_MESSAGE', receiver: other.id, offset:0, limit:20}, "CONTENT_TYPE" => 'application/json'
|
||||
response = JSON.parse(last_response.body)
|
||||
last_response.status.should == 200
|
||||
response.length.should == 2
|
||||
response[0]['notification_id'].should == msg1.id
|
||||
response[1]['notification_id'].should == msg2.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "share_recording" do
|
||||
|
||||
before(:each) do
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.ym
|
|||
require 'jam_ruby'
|
||||
|
||||
# uncomment this to see active record logs
|
||||
# ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base)
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base)
|
||||
|
||||
include JamRuby
|
||||
# put ActionMailer into test mode
|
||||
|
|
@ -36,7 +36,7 @@ Thread.new {
|
|||
sleep 30
|
||||
unless tests_started
|
||||
puts "tests are hung. exiting..."
|
||||
exit! 20
|
||||
#exit! 20
|
||||
end
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
# methods here all assume you are in /client
|
||||
|
||||
# enters text into the search sidebar
|
||||
def site_search(text, options = {})
|
||||
within('#searchForm') do
|
||||
fill_in "search-input", with: text
|
||||
end
|
||||
|
||||
find('#sidebar-search-expand').trigger(:click) if options[:expand]
|
||||
end
|
||||
|
||||
# goes to the musician tile, and tries to find a musician
|
||||
def find_musician(user)
|
||||
visit "/client#/musicians"
|
||||
|
||||
timeout = 30
|
||||
|
||||
start = Time.now
|
||||
# scroll by 100px until we find a user with the right id
|
||||
while page.all('#end-of-musician-list').length == 0
|
||||
page.execute_script('jQuery("#musician-filter-results").scrollTo("+=100px", 0, {axis:"y"})')
|
||||
found = page.all(".result-list-button-wrapper[data-musician-id='#{user.id}']")
|
||||
if found.length == 1
|
||||
return found[0]
|
||||
elsif found.length > 1
|
||||
raise "ambiguous results in musician list"
|
||||
end
|
||||
|
||||
if Time.now - start > timeout
|
||||
raise "unable to find musician #{user} within #{timeout} seconds"
|
||||
end
|
||||
end
|
||||
|
||||
raise "unable to find musician #{user}"
|
||||
end
|
||||
|
||||
# sends a text message in the chat interface.
|
||||
def send_text_message(msg, options={})
|
||||
find('#text-message-dialog') # assert that the dialog is showing already
|
||||
|
||||
within('#text-message-dialog form.text-message-box') do
|
||||
fill_in 'new-text-message', with: msg
|
||||
end
|
||||
find('#text-message-dialog .btn-send-text-message').trigger(:click)
|
||||
find('#text-message-dialog .previous-message-text', text: msg) unless options[:should_fail]
|
||||
|
||||
# close the dialog if caller specified close_on_send
|
||||
if options[:close_on_send]
|
||||
find('#text-message-dialog .btn-close-dialog', text: 'CLOSE').trigger(:click) if options[:close_on_send]
|
||||
page.should have_no_selector('#text-message-dialog')
|
||||
end
|
||||
|
||||
if options[:should_fail]
|
||||
find('#notification').should have_text(options[:should_fail])
|
||||
end
|
||||
end
|
||||
|
||||
def open_sidebar
|
||||
find('[layout-id="panelNotifications"] .panel-header').trigger(:click)
|
||||
end
|
||||
|
||||
def hover_intent(element)
|
||||
element.hover
|
||||
element.hover
|
||||
end
|
||||
|
|
@ -1,5 +1,20 @@
|
|||
include ApplicationHelper
|
||||
|
||||
# add a hover_intent method to element, so that you can do find(selector).hover_intent
|
||||
module Capybara
|
||||
module Node
|
||||
class Element
|
||||
def hover_intent
|
||||
hover
|
||||
hover
|
||||
hover
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# holds a single test's session name's, mapped to pooled session names
|
||||
$capybara_session_mapper = {}
|
||||
|
||||
|
|
@ -11,7 +26,7 @@ end
|
|||
# manages the mapped session name
|
||||
def mapped_session_name(session_name)
|
||||
return :default if session_name == :default # special treatment for the built-in session
|
||||
$capybara_session_mapper[session_name] ||= $capybara_session_mapper.length
|
||||
$capybara_session_mapper[session_name] ||= 'session_' + $capybara_session_mapper.length.to_s
|
||||
end
|
||||
|
||||
# in place of ever using Capybara.session_name directly,
|
||||
|
|
@ -56,15 +71,11 @@ end
|
|||
|
||||
def sign_in_poltergeist(user)
|
||||
visit signin_path
|
||||
fill_in "session_email", with: user.email
|
||||
fill_in "session_password", with: user.password
|
||||
fill_in "Email Address:", with: user.email
|
||||
fill_in "Password:", with: user.password
|
||||
click_button "SIGN IN"
|
||||
|
||||
if Capybara.javascript_driver == :poltergeist
|
||||
page.driver.set_cookie(:remember_token, user.remember_token)
|
||||
else
|
||||
page.driver.browser.manage.add_cookie :name => :remember_token, :value => user.remember_token
|
||||
end
|
||||
wait_until_curtain_gone
|
||||
end
|
||||
|
||||
def sign_out()
|
||||
|
|
@ -109,7 +120,7 @@ def wait_until_user(wait=Capybara.default_wait_time)
|
|||
end
|
||||
|
||||
def wait_until_curtain_gone
|
||||
should have_no_selector('.curtain')
|
||||
page.should have_no_selector('.curtain')
|
||||
end
|
||||
|
||||
def wait_to_see_my_track
|
||||
|
|
@ -353,4 +364,11 @@ end
|
|||
# wait for the easydropdown version of the specified select element to become visible
|
||||
def wait_for_easydropdown(select)
|
||||
find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown")]')
|
||||
end
|
||||
|
||||
# defaults to enter key (13)
|
||||
def send_key(keycode = 13)
|
||||
keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); $('#search-input').trigger(e);"
|
||||
page.driver.execute_script(keypress_script)
|
||||
|
||||
end
|
||||
Loading…
Reference in New Issue