VRFS-1483 merge

This commit is contained in:
Jonathan Kolyer 2014-03-18 15:13:03 +00:00
commit 3204de01e6
111 changed files with 2050 additions and 267 deletions

View File

@ -136,5 +136,5 @@ events.sql
cascading_delete_constraints_for_release.sql
events_social_description.sql
fix_broken_cities.sql
notifications_with_text.sql
emails.sql

View File

@ -0,0 +1 @@
ALTER TABLE notifications ADD COLUMN message TEXT;

View File

@ -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 {

View File

@ -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"

View File

@ -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 %>

View File

@ -0,0 +1,3 @@
<%= @sender_name %> says: <%= @note %>
To reply to this message, click here: <%= @root_url %>/client#/home/text-message/d1=<%= @sender_id %>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -561,6 +561,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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -780,7 +780,7 @@ module JamRuby
# if you came in from an invite and used the same email to signup,
# then we know you are a real human and that your email is valid.
# lucky! we'll log you in immediately
if invited_user.email.casecmp(user.email).zero?
if invited_user.email && invited_user.email.casecmp(user.email).zero?
user.email_confirmed = true
user.signup_token = nil
else

View File

@ -431,4 +431,11 @@ FactoryGirl.define do
test_emails 4.times.collect { Faker::Internet.safe_email }.join(',')
end
factory :notification, :class => JamRuby::Notification do
factory :notification_text_message do
description 'TEXT_MESSAGE'
message Faker::Lorem.characters(10)
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -51,7 +51,7 @@
}
function handleDeleteAudioProfile(audioProfileId) {
console.log("deleting audio profile: " + audioProfileId);
logger.debug("deleting audio profile: " + audioProfileId);
context.jamClient.TrackDeleteProfile(audioProfileId);

View File

@ -255,7 +255,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);
@ -264,7 +264,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);
@ -456,7 +456,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) {

View File

@ -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.

View File

@ -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

View File

@ -11,7 +11,7 @@
context.JK.BandSetupScreen = function (app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var friendSelectorDialog = null;
var inviteMusiciansUtil = null;
var invitationDialog = null;
var autoComplete = null;
var userNames = [];
@ -21,6 +21,7 @@
var nilOptionStr = '<option value=""></option>';
var nilOptionText = 'n/a';
var bandId = '';
var friendInput=null;
function is_new_record() {
return bandId.length == 0;
@ -62,7 +63,7 @@
$("#band-setup-step-1").show();
$("#band-setup-step-2").hide();
$('#band-invitee-input')
$(friendInput)
.unbind('blur')
.attr("placeholder", "Looking up friends...")
.prop('disabled', true)
@ -156,7 +157,7 @@
function createBandInvitations(bandId, onComplete) {
var callCount = 0;
var totalInvitations = 0;
$('#selected-band-invitees .invitation').each(function (index, invitation) {
$('#selected-friends-band .invitation').each(function (index, invitation) {
callCount++;
totalInvitations++;
var userId = $(invitation).attr('user-id');
@ -179,16 +180,13 @@
}
function beforeShow(data) {
userNames = [];
userIds = [];
userPhotoUrls = [];
inviteMusiciansUtil.clearSelections();
bandId = data.id == 'new' ? '' : data.id;
resetForm();
}
function afterShow(data) {
friendSelectorDialog.setCallback(friendSelectorCallback);
loadFriends();
inviteMusiciansUtil.loadFriends();
if (!is_new_record()) {
$("#band-setup-title").html("edit band");
@ -240,38 +238,6 @@
});
}
// TODO: this is repeated in createSession.js.erb
function loadFriends() {
rest.getFriends({ id: context.JK.currentUserId })
.done(function (friends) {
$.each(friends, function () {
userNames.push(this.name);
userIds.push(this.id);
userPhotoUrls.push(this.photo_url);
});
var autoCompleteOptions = {
lookup: { suggestions: userNames, data: userIds },
onSelect: addInvitation
};
$('#band-invitee-input').attr("placeholder", "Type a friend\'s name").prop('disabled', false);
if (!autoComplete) {
autoComplete = $('#band-invitee-input').autocomplete(autoCompleteOptions);
}
else {
autoComplete.setOptions(autoCompleteOptions);
}
$(".autocomplete").width("150px");
})
.fail(function () {
$('#band-invitee-input').attr("placeholder", "Unable to lookup friends");
app.ajaxError(arguments)
});
}
function loadGenres(selectedGenres) {
$("#band-genres").empty();
@ -393,13 +359,6 @@
}
}
function friendSelectorCallback(newSelections) {
var keys = Object.keys(newSelections);
for (var i = 0; i < keys.length; i++) {
addInvitation(newSelections[keys[i]].userName, newSelections[keys[i]].userId);
}
}
function addInvitation(value, data) {
if ($('#selected-band-invitees div[user-id=' + data + ']').length === 0) {
var template = $('#template-band-invitation').html();
@ -462,10 +421,6 @@
$('#btn-band-setup-save').click(saveBand);
$('#btn-band-choose-friends').click(function () {
friendSelectorDialog.showDialog(selectedFriendIds);
});
$('#band-country').on('change', function (evt) {
evt.stopPropagation();
loadRegions();
@ -493,10 +448,13 @@
$('div[layout-id="band/setup"] .btn-facebook-invitation').click(function () {
invitationDialog.showFacebookDialog();
});
$(friendInput).focus(function() { $(this).val(''); })
}
function initialize(invitationDialogInstance, friendSelectorDialogInstance) {
friendSelectorDialog = friendSelectorDialogInstance;
function initialize(invitationDialogInstance, inviteMusiciansUtilInstance) {
inviteMusiciansUtil = inviteMusiciansUtilInstance;
friendInput = inviteMusiciansUtil.inviteBandCreate('#band-setup-invite-musicians', "<div class='left w70'>If your bandmates are already on JamKazam, start typing their names in the box below, or click the Choose Friends button to select them.</div>");
invitationDialog = invitationDialogInstance;
events();

View File

@ -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.

View File

@ -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;
}

View File

@ -1,5 +1,6 @@
//= require jquery
//= require jquery.queryparams
//= require AAA_Log
//= require AAC_underscore
//= require globals
//= require jamkazam

View File

@ -11,6 +11,7 @@
var inviteMusiciansUtil = null;
var MAX_GENRES = 1;
var sessionSettings = {};
var friendInput = null;
function beforeShow(data) {
inviteMusiciansUtil.clearSelections();
@ -60,7 +61,7 @@
context.JK.dropdown($('#musician-access', $form));
context.JK.dropdown($('#fan-access', $form));
$('#friend-input')
$(friendInput)
.unbind('blur')
.attr("placeholder", "Looking up friends...")
.prop('disabled', true)
@ -233,7 +234,7 @@
invitationDialog.showFacebookDialog(e);
});
$('#friend-input').focus(function() { $(this).val(''); })
$(friendInput).focus(function() { $(this).val(''); })
}
function toggleMusicianAccess() {
@ -306,7 +307,7 @@
function initialize(invitationDialogInstance, inviteMusiciansUtilInstance) {
invitationDialog = invitationDialogInstance;
inviteMusiciansUtil = inviteMusiciansUtilInstance;
inviteMusiciansUtil.inviteSessionCreate('#create-session-invite-musicians');
friendInput = inviteMusiciansUtil.inviteSessionCreate('#create-session-invite-musicians', "<div style='margin-right:140px;'>Start typing friends' names or:</div>"); //'
events();
loadBands();
loadSessionSettings();

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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() {

View File

@ -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);
}

View File

@ -14,18 +14,34 @@
var autoComplete = null;
var rest = context.JK.Rest();
var inviteAction = 'create'; // create/update
var friendInput = null;
var updateSessionID = null;
var addInstructions = '';
this.inviteSessionCreate = function(elemSelector) {
inviteAction = 'create';
function _initInvite(elemSelector, iAction, instructions) {
addInstructions = instructions;
inviteAction = iAction;
friendInput = '#friend-input-'+inviteAction;
_appendFriendSelector($(elemSelector));
return friendInput;
}
this.inviteSessionCreate = function(elemSelector, instructions) {
return _initInvite(elemSelector, 'create', instructions)
};
this.inviteBandCreate = function(elemSelector, instructions) {
return _initInvite(elemSelector, 'band', instructions)
};
this.inviteSessionUpdate = function(elemSelector, sessionId) {
this.clearSelections();
updateSessionID = sessionId;
friendSelectorDialog.setCallback(friendSelectorCallback);
inviteAction = 'update';
friendInput = '#friend-input-'+inviteAction;
if (0 == $(elemSelector + ' .friendbox').length) {
_appendFriendSelector($(elemSelector));
$('#btn-save-invites').click(function() {
@ -52,6 +68,7 @@
existingInvites = [];
updateSessionID = null;
$('.selected-friends').empty();
$(friendInput).val('');
};
this.loadFriends = function() {
@ -64,27 +81,21 @@
userIds.push(this.id);
userPhotoUrls.push(this.photo_url);
});
var autoCompleteOptions = {
lookup: { suggestions: userNames, data: userIds },
onSelect: addInvitation,
serviceUrl: '/api/search.json?srch_sessinv=1',
minChars: 3,
autoSelectFirst: true
};
$('#friend-input').attr("placeholder", "Type a friend\'s name").prop('disabled', false);
if (!autoComplete) {
autoComplete = $('#friend-input').autocomplete(autoCompleteOptions);
} else {
autoComplete.setOptions(autoCompleteOptions);
if (friendInput) {
var autoCompleteOptions = {
lookup: { suggestions: userNames, data: userIds },
onSelect: addInvitation,
serviceUrl: '/api/search.json?srch_sessinv=1',
minChars: 3,
autoSelectFirst: true
};
$(friendInput).attr("placeholder", "Type a friend\'s name").prop('disabled', false)
autoComplete = $(friendInput).autocomplete(autoCompleteOptions);
$(".autocomplete").width("150px");
}
$(".autocomplete").width("150px");
})
.fail(function() {
$('#friend-input').attr("placeholder", "Unable to lookup friends");
$(friendInput).attr("placeholder", "Unable to lookup friends");
app.ajaxError(arguments);
});
}
@ -114,11 +125,11 @@
userName: value,
imageStyle: imgStyle});
$('.selected-friends').append(invitationHtml);
$('#friend-input').select();
$(friendInput).select();
invitedFriends.push(data);
} else {
$('#friend-input').select();
$(friendInput).select();
context.alert('Invitation already exists for this musician.');
}
}
@ -188,9 +199,12 @@
}
function _friendSelectorHTML() {
var fInput = friendInput ? friendInput.substring(1,friendInput.length) : '';
return context.JK.fillTemplate($('#template-session-invite-musicians').html(),
{choose_friends_id: 'btn-choose-friends-'+inviteAction,
selected_friends_id: 'selected-friends-'+inviteAction});
selected_friends_id: 'selected-friends-'+inviteAction,
friend_input: fInput,
instructions: addInstructions});
}
function _appendFriendSelector(elemSelector) {
@ -201,10 +215,14 @@
invitedFriends.map(function(uid) { obj[uid] = true; });
friendSelectorDialog.showDialog(obj);
});
if ('update' == inviteAction) {
$(friendInput).hide();
}
};
this.initialize = function(friendSelectorDialogInstance) {
friendSelectorDialog = friendSelectorDialogInstance;
return this;
};
return this;

View File

@ -576,7 +576,7 @@
/** check if the server is alive */
function serverHealthCheck(options) {
console.log("serverHealthCheck")
logger.debug("serverHealthCheck")
return $.ajax({
type: "GET",
url: "/api/versioncheck"
@ -907,6 +907,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;
}
@ -986,6 +1007,8 @@
this.getShareRecording = getShareRecording;
this.tweet = tweet;
this.createFbInviteUrl = createFbInviteUrl;
this.createTextMessage = createTextMessage;
this.getNotifications = getNotifications;
return this;
};

View File

@ -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."})
});
}
};

View File

@ -1,5 +1,6 @@
//= require jquery
//= require jquery.queryparams
//= require AAA_Log
//= require AAC_underscore
//= require globals
//= require jamkazam

View File

@ -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) {

View File

@ -48,7 +48,7 @@
});
}
else {
console.log("workin fool: %o", working)
}
}

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}

View File

@ -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');
});
}

View File

@ -463,7 +463,7 @@
})
}
else {
console.log("no copy-to-clipboard capabilities")
logger.debug("no copy-to-clipboard capabilities")
}
}
}

View File

@ -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,6 +140,10 @@
$.each(response, function(index, val) {
if(val.description == 'TEXT_MESSAGE') {
val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html();
}
// fill in template for Connect pre-click
var template = $('#template-notification-panel').html();
var notificationHtml = context.JK.fillTemplate(template, {
@ -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);

View File

@ -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);

View File

@ -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() {

View File

@ -654,13 +654,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;
};
@ -685,7 +685,7 @@
badAudioConfigs.push(allAudioConfigs[i]);
}
}
console.log("badAudioConfigs=%o", badAudioConfigs);
logger.debug("badAudioConfigs=%o", badAudioConfigs);
return badAudioConfigs;
};
@ -732,6 +732,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() );

View File

@ -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(){

View File

@ -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);
}
}

View File

@ -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

View File

@ -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');

View File

@ -41,6 +41,7 @@
*= require ./localRecordingsDialog
*= require ./serverErrorDialog
*= require ./leaveSessionWarning
*= require ./textMessageDialog
*= require ./terms
*= require ./createSession
*= require ./feed

View File

@ -429,7 +429,7 @@ ul.shortcuts {
padding:2px;
}
.account-home, .band-setup, .audio, .get-help, .download-app, .invite-friends {
.account-home, .band-setup, .audio, .get-help, .download-app, .community-forum, .invite-friends {
border-bottom:1px;
border-style:solid;
border-color:#ED3618;

View File

@ -7,6 +7,10 @@
z-index:999;
}
.bubble.musician-bubble {
width:410px;
}
.bubble h2 {
padding:6px 0px;
text-align:center;

View File

@ -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;

View File

@ -31,3 +31,8 @@
}
}
#notification p .text-message {
white-space: pre-wrap;
word-wrap: break-word;
}

View File

@ -5,7 +5,7 @@
margin: 0 auto;
position: absolute;
text-align: center;
left: 25%;
left: 17%;
width: 50%;
z-index:-1;

View File

@ -261,5 +261,15 @@
margin-left:-3px;
}
}
#sidebar-notification-list {
.text-message {
word-wrap:break-word;
}
.more-text-available {
}
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -28,11 +28,7 @@ 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
render :layout => 'client'
else
redirect_to root_url
end
render :layout => 'client'
end
AUTHED = %W{friend}

View File

@ -5,6 +5,16 @@ class SessionsController < ApplicationController
def new
@login_error = false
@sso = params[:sso]
@send_back_to = request.headers['REFERER']
params[:send_back_to] = @send_back_to
if current_user
# send them on their way
complete_sign_in(current_user)
return
end
render :layout => "landing"
end
@ -13,6 +23,8 @@ class SessionsController < ApplicationController
if user.nil?
@login_error = true
@sso = params[:sso]
@send_back_to = params[:send_back_to]
render 'new', :layout => "landing"
else
@ -133,16 +145,30 @@ class SessionsController < ApplicationController
render 'oauth_complete', :layout => "landing"
end
def redirect_after_signin(default)
redirect_to(params['redirect-to'].blank? ? default : params['redirect-to'])
end
def redirect_to_forums_after_signin
redirect_to("#{Rails.application.config.vanilla_login_url}?client_id=#{Rails.application.config.vanilla_client_id}&Target=#{ERB::Util.url_encode(params[:send_back_to].blank? ? '/' : params[:send_back_to])}")
end
def redirect_to_support_after_signin(user)
# generate multipass token and sign it
multipass = DeskMultipass.new(user)
callback_url = Rails.application.config.multipass_callback_url
redirect_to "#{callback_url}?multipass=#{multipass.token}&signature=#{multipass.signature}"
end
def complete_sign_in(user)
sign_in user
if !params[:sso].nil? && params[:sso] == "desk"
# generate multipass token and sign it
multipass = DeskMultipass.new(user)
callback_url = SampleApp::Application.config.multipass_callback_url
redirect_to "#{callback_url}?multipass=#{multipass.token}&signature=#{multipass.signature}"
if params[:sso] == "desk"
redirect_to_support_after_signin(user)
elsif params[:sso] == 'forums'
redirect_to_forums_after_signin
else
redirect_back_or client_url
redirect_after_signin(client_path)
end
end

View File

@ -105,7 +105,7 @@ class UsersController < ApplicationController
@fb_signup = load_facebook_signup(params)
# check if the email specified by @fb_signup already exists in the databse--if so, log them in and redirect
# check if the email specified by @fb_signup already exists in the database--if so, log them in and redirect
if @fb_signup && @fb_signup.email
user = User.find_by_email_and_email_confirmed(@fb_signup, true)
if user
@ -117,7 +117,7 @@ class UsersController < ApplicationController
end
end
# check if the uid specified by @fb_signup already exists in the databse--if so, log them in and redirect
# check if the uid specified by @fb_signup already exists in the database--if so, log them in and redirect
if @fb_signup && @fb_signup.uid
user_authorization = UserAuthorization.find_by_uid_and_provider(@fb_signup.uid, 'facebook')
# update user_authorization for user because this is fresher
@ -199,10 +199,10 @@ class UsersController < ApplicationController
@slides = [
Slide.new("JamKazam Overview", "web/carousel_musicians.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1"),
Slide.new("Getting Started", "web/carousel_fans.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1"),
Slide.new("Getting Started", "web/carousel_fans.jpg", "http://www.youtube.com/embed/VexH4834o9I?autoplay=1"),
Slide.new("Playing in a Session", "web/carousel_bands.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1"),
Slide.new("JamKazam Overview", "web/carousel_musicians.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1"),
Slide.new("Getting Started", "web/carousel_fans.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1"),
Slide.new("Getting Started", "web/carousel_fans.jpg", "http://www.youtube.com/embed/VexH4834o9I?autoplay=1"),
Slide.new("Playing in a Session", "web/carousel_bands.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1")
]

View File

@ -0,0 +1,63 @@
require 'base64'
require 'js_connect'
class VanillaForumsController < ApplicationController
@@log = Logging.logger[VanillaForumsController]
# displays the embedded forum
# see http://vanillaforums.com/blog/jsconnect-technical-documentation-for-embedded-sso/
def show
user = {name: '', photourl: ''}
if current_user
user = {email: current_user.email, name: current_user.username,
photourl: current_user.profile_pic,
uniqueid: current_user.username}
end
user.merge!({client_id: Rails.application.config.vanilla_client_id})
# json encode the user
json = ActiveSupport::JSON.encode(user);
# base 64 encode the user json
signature_string = Base64.strict_encode64(json)
# Sign the signature string with current timestamp using hmac sha1
signature = Digest::HMAC.hexdigest(signature_string + ' ' +
Time.now.to_i.to_s, Rails.application.config.vanilla_secret, Digest::SHA1)
# build the final sso string
@vanilla_sso = "#{signature_string} #{signature} #{Time.now.to_i} hmacsha1"
end
# callback for vanilla authentication
# see http://vanillaforums.com/blog/jsconnect-technical-documentation
# ruby jsconnect client library: https://github.com/vanillaforums/jsConnectRuby
def authenticate
user = {}
if current_user
user = {'email' => current_user.email, 'name' => current_user.name,
'photourl' => current_user.resolved_photo_url,
'uniqueid' => current_user.id}
@@log.debug("user is logged in: #{user}")
else
@@log.debug("user is not logged in")
end
render :json => JsConnect::getJsConnectString(user, request,
Rails.application.config.vanilla_client_id, Rails.application.config.vanilla_secret)
end
# only for testing; routes are conditionally based on test ENV
def fake_root
render layout: 'web'
end
# only for testing; routes are conditionally based on test ENV
def fake_jsconnect
render layout: 'web'
end
end

View File

@ -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
@ -49,7 +54,7 @@ module SessionsHelper
def sign_out
current_user = nil
cookies.delete(:remember_token)
cookies.delete(:remember_token, domain: Rails.application.config.session_cookie_domain)
end
def redirect_back_or(default)

View File

@ -0,0 +1,3 @@
object @notification
attributes :id

View File

@ -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

View File

@ -93,20 +93,8 @@
<div id="band-setup-step-2" class="content-wrapper" style="padding:10px 35px 10px 35px; display:none;">
<br/>
<h2>Step 2: Add Band Members</h2><br/>
<div class="left w70">If your bandmates are already on JamKazam, start typing their names in the box<br/> below, or click the Choose Friends button to select them.</div>
<div class="right" layout-link="select-friends">
<a href="#" id="btn-band-choose-friends" class="button-grey right">CHOOSE FRIENDS</a>
</div>
<br clear="all" />
<br />
<div class="friendbox">
<div id="selected-band-invitees"></div>
<input id="band-invitee-input" type="text" placeholder="Looking up friends..." width="150px" />
</div>
<br/><br/>
If your bandmates are not on JamKazam yet, use any of the options below to invite them to join the service.<br/><br/>
<div id="band-setup-invite-musicians"></div>
If your bandmates are not on JamKazam yet, use any of the options below to invite them to join the service.<br/><br/>
<div class="left mr20">
<div class="left">
<a class="btn-email-invitation">

View File

@ -6,7 +6,7 @@
<!-- footer links -->
<div id="footer-links">
<%= link_to "about", corp_about_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "news", corp_news_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "media", corp_media_center_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "contact", corp_contact_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "privacy", corp_privacy_path, :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "terms of service", corp_terms_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "help", corp_help_path , :rel=>"external" %>
<%= link_to "about", corp_about_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "news", corp_news_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "media", corp_media_center_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "contact", corp_contact_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "privacy", corp_privacy_path, :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "terms of service", corp_terms_path , :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "community forum", Rails.application.config.vanilla_url, :rel=>"external" %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "help", corp_help_path , :rel=>"external" %>
</div>
<%= render "clients/recordingManager" %>

View File

@ -1,4 +1,4 @@
<div id="band-hover" class="hidden bubble">
<div id="band-hover" class="hidden bubble band-bubble">
</div>

View File

@ -1,4 +1,4 @@
<div id="fan-hover" class="hidden bubble">
<div id="fan-hover" class="hidden bubble fan-bubble">
</div>

View File

@ -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>

View File

@ -17,16 +17,14 @@
<div class="right" layout-link="select-friends">
<a href="#" class="btn-choose-friends button-grey" id="{choose_friends_id}">CHOOSE FRIENDS</a>
</div>
<div style="margin-right:140px;">
Start typing friends'' names or:
</div>
{instructions}
<div class="clearall"></div>
</div>
<br />
<!-- friend invitation box -->
<div class="friendbox">
<div class="selected-friends" id="{selected_friends_id}"></div>
<input id="friend-input" type="text" placeholder="Looking up friends..." />
<input id="{friend_input}" type="text" placeholder="Looking up friends..." />
</div>
</script>

View File

@ -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,

View File

@ -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>

View File

@ -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 />

View File

@ -217,6 +217,7 @@
</li>
</script>
<!-- Chat panel template -->
<script type="text/template" id="template-chat-panel">
<li>

View File

@ -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}}'

View File

@ -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 %>

View File

@ -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();
@ -132,9 +142,6 @@
var friendSelectorDialog = new JK.FriendSelectorDialog(JK.app);
friendSelectorDialog.initialize();
var inviteMusiciansUtil = new JK.InviteMusiciansUtil(JK.app);
inviteMusiciansUtil.initialize(friendSelectorDialog);
var userDropdown = new JK.UserDropdown(JK.app);
JK.UserDropdown = userDropdown;
userDropdown.initialize(invitationDialog);
@ -144,13 +151,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();
@ -177,11 +184,15 @@
JK.GenreSelectorHelper.initialize();
JK.Banner.initialize();
var inviteMusiciansUtil1 = new JK.InviteMusiciansUtil(JK.app);
inviteMusiciansUtil1.initialize(friendSelectorDialog);
var createSessionScreen = new JK.CreateSessionScreen(JK.app);
createSessionScreen.initialize(invitationDialog, inviteMusiciansUtil);
createSessionScreen.initialize(invitationDialog, inviteMusiciansUtil1);
var inviteMusiciansUtil2 = new JK.InviteMusiciansUtil(JK.app);
inviteMusiciansUtil2.initialize(friendSelectorDialog);
var bandSetupScreen = new JK.BandSetupScreen(JK.app);
bandSetupScreen.initialize(invitationDialog, friendSelectorDialog);
bandSetupScreen.initialize(invitationDialog, inviteMusiciansUtil2);
var bandSetupPhotoScreen = new JK.BandSetupPhotoScreen(JK.app);
bandSetupPhotoScreen.initialize();
@ -197,13 +208,15 @@
findSessionScreen.initialize(sessionLatency);
var findMusicianScreen = new JK.FindMusicianScreen(JK.app);
findMusicianScreen.initialize();
findMusicianScreen.initialize(textMessageDialog);
var findBandScreen = new JK.FindBandScreen(JK.app);
findBandScreen.initialize();
var inviteMusiciansUtil3 = new JK.InviteMusiciansUtil(JK.app);
inviteMusiciansUtil3.initialize(friendSelectorDialog);
var sessionScreen = new JK.SessionScreen(JK.app);
sessionScreen.initialize(localRecordingsDialog, recordingFinishedDialog, inviteMusiciansUtil);
sessionScreen.initialize(localRecordingsDialog, recordingFinishedDialog, inviteMusiciansUtil3);
var sessionSettingsDialog = new JK.SessionSettingsDialog(JK.app, sessionScreen);
sessionSettingsDialog.initialize();

View File

@ -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"

View File

@ -63,7 +63,7 @@
<div id="copyright">Copyright &copy; <%= Time.now.year %> JamKazam, Inc. All Rights Reserved</div>
<!-- footer links -->
<div id="footer-links"><%= link_to "about", corp_about_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "news", corp_news_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "media", corp_media_center_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "contact", corp_contact_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "privacy", corp_privacy_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "terms of service", corp_terms_path %>&nbsp;&nbsp;|&nbsp;<%= link_to "help", corp_help_path %></div>
<div id="footer-links"><%= link_to "about", corp_about_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "news", corp_news_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "media", corp_media_center_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "contact", corp_contact_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "privacy", corp_privacy_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "terms of service", corp_terms_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "community forum", Rails.application.config.vanilla_url, :rel=>"external" %>&nbsp;&nbsp;|&nbsp;<%= link_to "help", corp_help_path %></div>
<div id="version"><%= version %></div>
</div>

View File

@ -16,8 +16,9 @@
Enter your email address and password:
<div>
<%= form_for(:session, url: sessions_path) do |f| %>
<input type="hidden" name="sso" value="<%= params[:sso] %>">
<%= form_for(:session, url: signin_path + (request.query_string.blank? ? '' : '?' + request.query_string)) do |f| %>
<input type="hidden" name="sso" value="<%= @sso %>">
<input type="hidden" name="send_back_to" value="<%= @send_back_to %>">
<fieldset name="text-input" class="<%= 'login-error' if @login_error %>">
<div class="field email">

View File

@ -31,6 +31,7 @@
</ul>
</li>
<li class="download-app"><%= link_to "Download App", downloads_path, :rel => "external" %></li>
<li class="community-forum"><%= link_to "Community Forum", Rails.application.config.vanilla_url, :rel => "external" %></li>
<li class="get-help"><%= link_to "Get Help", 'https://jamkazam.desk.com/', :rel => "external" %></li>
<li class="sign-out"><%= link_to "Sign Out", signout_path, method: "delete" %></li>

View File

@ -0,0 +1 @@
%h1 welcome to fake login page

View File

@ -0,0 +1 @@
%h1 welcome to fake vanilla forums

View File

@ -218,5 +218,18 @@ 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
# these are production values. we should have a test server, but would require us to set one up
# we do have some 'fake pages' in the vanilla_forums_controller.rb to get close
config.vanilla_client_id = 'www'
config.vanilla_secret = 'bibbitybobbityslipperyslopes'
config.vanilla_url = 'http://forums.jamkazam.com'
config.vanilla_login_url = 'http://forums.jamkazam.com/entry/jsconnect'
# we have to do this for a while until all www.jamkazam.com cookies are gone,
# and only .jamkazam.com cookies are around.. 2016?
config.middleware.insert_before "ActionDispatch::Cookies", "Middlewares::ClearDuplicatedSession"
end
end

View File

@ -43,7 +43,7 @@ SampleApp::Application.configure do
# For testing omniauth
OmniAuth.config.test_mode = true
config.websocket_gateway_enable = true
config.websocket_gateway_enable = false
config.websocket_gateway_port = 6769
config.websocket_gateway_uri = "ws://localhost:#{config.websocket_gateway_port}/websocket"
@ -69,5 +69,10 @@ SampleApp::Application.configure do
config.use_promos_on_homepage = false
config.use_cached_session_scores = true
config.session_cookie_domain = nil
config.vanilla_url = '/forums'
config.vanilla_login_url = '/forums/entry/jsconnect'
end

View File

@ -1,4 +1,3 @@
unless $rails_rake_task
JamWebEventMachine.start
@ -14,5 +13,6 @@ unless $rails_rake_task
:rabbitmq_host => APP_CONFIG.rabbitmq_host,
:rabbitmq_port => APP_CONFIG.rabbitmq_port)
end
end
end

View File

@ -17,7 +17,8 @@ SampleApp::Application.routes.draw do
match '/congratulations_fan', to: 'users#congratulations_fan'
match '/downloads', to: 'users#downloads'
match '/signin', to: 'sessions#new'
match '/signin', to: 'sessions#new', via: :get
match '/signin', to: 'sessions#create', via: :post
match '/signout', to: 'sessions#destroy', via: :delete
# oauth
@ -84,8 +85,16 @@ SampleApp::Application.routes.draw do
if Rails.env == "test"
match '/test_connection', to: 'sessions#connection_state', :as => :connection_state
# vanilla forums 'fake methods'
match '/forums', to: 'vanilla_forums#fake_root'
match '/forums/entry/jsconnect', to: 'vanilla_forums#fake_jsconnect'
end
# vanilla forums sso
match '/forums/sso', to: 'vanilla_forums#authenticate'
scope '/corp' do
# about routes
match '/about', to: 'corps#about', as: 'corp_about'
@ -219,6 +228,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

99
web/lib/js_connect.rb Normal file
View File

@ -0,0 +1,99 @@
# This module contains the client code for Vanilla jsConnect single sign on
# Author:: Todd Burry (mailto:todd@vanillaforums.com)
# Version:: 1.0b
# Copyright:: Copyright 2008, 2009 Vanilla Forums Inc.
# License http://www.opensource.org/licenses/gpl-2.0.php GPLv2
module JsConnect
@@log = Logging.logger[JsConnect]
def JsConnect.error(code, message)
return {"error" => code, "message" => message}
end
def JsConnect.getJsConnectString(user, request = {}, client_id = "", secret = "", secure = true)
error = nil
timestamp = request["timestamp"].to_i
current_timestamp = JsConnect.timestamp
if secure
# Make sure the request coming in is signed properly
if !request['client_id']
error = JsConnect.error('invalid_request', 'The client_id parameter is missing.')
elsif request['client_id'] != client_id
error = JsConnect.error('invalid_client', "Unknown client #{request['client_id']}.")
elsif request['timestamp'].nil? and request['signature'].nil?
@@log.debug("no timestamp right? #{request['timestamp']}, #{request['signature']}")
if user and !user.empty?
error = {'name' => user['name'], 'photourl' => user['photourl']}
else
error = {'name' => '', 'photourl' => ''}
end
elsif request['timestamp'].nil?
error = JsConnect.error('invalid_request', 'The timestamp is missing or invalid.')
elsif !request['signature']
error = JsConnect.error('invalid_request', 'The signature is missing.')
elsif (current_timestamp - timestamp).abs > 30 * 60
error = JsConnect.error('invalid_request', 'The timestamp is invalid.')
else
# Make sure the timestamp's signature checks out.
timestamp_sig = Digest::MD5.hexdigest(timestamp.to_s + secret)
if timestamp_sig != request['signature']
error = JsConnect.error('access_denied', 'Signature invalid.')
end
end
end
if error
@@log.debug("not valid request: #{error}")
result = error
elsif user and !user.empty?
result = user.clone
@@log.debug("logging in: #{error}")
JsConnect.signJsConnect(result, client_id, secret, true)
else
@@log.debug("anonymous")
result = {"name" => "", "photourl" => ""}
end
json = ActiveSupport::JSON.encode(result);
if request["callback"]
return "#{request["callback"]}(#{json});"
else
return json
end
end
def JsConnect.signJsConnect(data, client_id, secret, set_data = false)
# Build the signature string. This is essentially a querystring representation of data, sorted by key
keys = data.keys.sort { |a,b| a.downcase <=> b.downcase }
sig_str = ""
keys.each do |key|
if sig_str.length > 0
sig_str += "&"
end
value = data[key]
@@log.debug("key #{key}, value #{value}")
sig_str += CGI.escape(key) + "=" + CGI.escape(value)
end
signature = Digest::MD5.hexdigest(sig_str + secret);
if set_data
data["clientid"] = client_id
data["signature"] = signature
end
return signature
end
def JsConnect.timestamp
return Time.now.to_i
end
end

View File

@ -0,0 +1,70 @@
# http://astashov.github.io/2011/02/26/conflict-of-session-cookies-with-different-domains-in-rails-3.html
# We had to do this when we changed from www.jamkazam.com to .jamkazam.com as the cookie served out
module Middlewares
class ClearDuplicatedSession
@@log = Logging.logger[ClearDuplicatedSession]
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers.each do|k,v|
if k == 'Set-Cookie' && v.start_with?(get_session_key(env))
bits = v.split(';')
if bits.length > 0
cookie_name_value = bits[0].split('=')
if cookie_name_value.length == 1 && Rails.application.config.session_cookie_domain
# this path indicates there is no value for the remember_token, i.e., it's being deleted
::Rack::Utils.set_cookie_header!(
headers, # contains response headers
get_session_key(env), # gets the cookie session name, '_session_cookie' - for this example
{ :value => '', :path => '/', :expires => Time.at(0) })
end
end
end
end
if there_are_more_than_one_session_key_in_cookies?(env)
delete_session_cookie_for_current_domain(env, headers)
end
[status, headers, body]
end
private
def there_are_more_than_one_session_key_in_cookies?(env)
entries = 0
offset = 0
while offset = env["HTTP_COOKIE"].to_s.index(get_session_key(env), offset)
entries += 1
offset += 1
end
entries > 1
end
# Sets expiration date = 1970-01-01 to the cookie, this way browser will
# note the cookie is expired and will delete it
def delete_session_cookie_for_current_domain(env, headers)
@@log.debug "deleting default domain session cookie"
::Rack::Utils.set_cookie_header!(
headers, # contains response headers
get_session_key(env), # gets the cookie session name, '_session_cookie' - for this example
{ :value => '', :path => '/', :expires => Time.at(0) }
)
end
def get_session_key(env)
'remember_token'
end
end
end

View File

@ -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

View File

@ -34,11 +34,9 @@ describe SessionsController do
post :create, :session => @attr
response.should redirect_to(client_url)
end
end
describe "create_oauth" do
describe "create_oauth" do
describe "twitter" do

View File

@ -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

View File

@ -97,7 +97,7 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
end
it {
user.subscribe_email.should be_true
user.subscribe_email.should be_true # we haven't user.reload yet
should have_selector('h1', text: 'my account')
should have_selector('#notification h2', text: 'Profile Changed')
user.reload

View File

@ -82,20 +82,10 @@ describe "Authentication", :js => true, :type => :feature, :capybara_feature =>
describe "after signing in" do
it "should render the desired protected page" do
page.should have_title("JamKazam | Edit user")
end
describe "when signing in again" do
describe "when attempting to sign in again, should render the signed-in client page" do
before do
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "SIGN IN"
end
it "should render the signed-in client page" do
# it now goes to /music_sessions
page.should have_title("JamKazam")
page.should have_selector('h2', text: "musicians")
end

Some files were not shown because too many files have changed in this diff Show More