paypal support
This commit is contained in:
parent
458637b1de
commit
5e04f72d8e
|
|
@ -369,4 +369,5 @@ second_ed.sql
|
||||||
second_ed_v2.sql
|
second_ed_v2.sql
|
||||||
retailers_v2.sql
|
retailers_v2.sql
|
||||||
retailer_interest.sql
|
retailer_interest.sql
|
||||||
connection_role.sql
|
connection_role.sql
|
||||||
|
retailer_payment_split.sql
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE retailers ADD COLUMN payment VARCHAR;
|
||||||
|
ALTER TABLE lesson_bookings ADD COLUMN payment VARCHAR;
|
||||||
|
|
@ -40,6 +40,7 @@ require "jam_ruby/errors/state_error"
|
||||||
require "jam_ruby/errors/jam_argument_error"
|
require "jam_ruby/errors/jam_argument_error"
|
||||||
require "jam_ruby/errors/jam_record_not_found"
|
require "jam_ruby/errors/jam_record_not_found"
|
||||||
require "jam_ruby/errors/conflict_error"
|
require "jam_ruby/errors/conflict_error"
|
||||||
|
require "jam_ruby/errors/pay_pal_client_error"
|
||||||
require "jam_ruby/lib/app_config"
|
require "jam_ruby/lib/app_config"
|
||||||
require "jam_ruby/lib/s3_manager_mixin"
|
require "jam_ruby/lib/s3_manager_mixin"
|
||||||
require "jam_ruby/lib/s3_public_manager_mixin"
|
require "jam_ruby/lib/s3_public_manager_mixin"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
module JamRuby
|
||||||
|
class PayPalClientError < StandardError
|
||||||
|
|
||||||
|
attr_accessor :errors
|
||||||
|
def initialize(data)
|
||||||
|
if data.respond_to?('has_key?')
|
||||||
|
self.errors = data
|
||||||
|
else
|
||||||
|
self.errors = {:message=>data.to_s}
|
||||||
|
end
|
||||||
|
end # initialize
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
s=super
|
||||||
|
s << ", errors: #{errors.inspect}" if self.errors.any?
|
||||||
|
s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -531,7 +531,7 @@ module JamRuby
|
||||||
distribution = teacher_distribution_price_in_cents(target)
|
distribution = teacher_distribution_price_in_cents(target)
|
||||||
|
|
||||||
if education
|
if education
|
||||||
(distribution * 0.0625).round
|
(distribution * 0.0625).round # 0.0625 is 1/4th of 25%
|
||||||
else
|
else
|
||||||
distribution
|
distribution
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -216,11 +216,20 @@ module JamRuby
|
||||||
|
|
||||||
self.status = STATUS_COMPLETED
|
self.status = STATUS_COMPLETED
|
||||||
|
|
||||||
|
# RETAILERPAY2
|
||||||
|
|
||||||
|
|
||||||
if success && lesson_booking.requires_teacher_distribution?(self)
|
if success && lesson_booking.requires_teacher_distribution?(self)
|
||||||
|
is_education_school_on_school = lesson_booking.school_on_school_payment?
|
||||||
|
|
||||||
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
|
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
|
||||||
if lesson_booking.school_on_school_payment?
|
if is_education_school_on_school
|
||||||
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, true)
|
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# this is a bit of a hack, in how the code is structured.
|
||||||
|
# but basically, the distributions calculated are too dynamic for the above code.
|
||||||
|
# if this is a retailer
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.save
|
if self.save
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ module JamRuby
|
||||||
|
|
||||||
SOURCE_RECURLY = 'recurly'
|
SOURCE_RECURLY = 'recurly'
|
||||||
SOURCE_IOS = 'ios'
|
SOURCE_IOS = 'ios'
|
||||||
|
SOURCE_PAYPAL = 'paypal'
|
||||||
|
|
||||||
belongs_to :retailer, class_name: 'JamRuby::Retailer'
|
belongs_to :retailer, class_name: 'JamRuby::Retailer'
|
||||||
belongs_to :user, class_name: 'JamRuby::User'
|
belongs_to :user, class_name: 'JamRuby::User'
|
||||||
|
|
@ -166,7 +167,7 @@ module JamRuby
|
||||||
# individual subscriptions will end up create their own sale (you can't have N subscriptions in one sale--recurly limitation)
|
# individual subscriptions will end up create their own sale (you can't have N subscriptions in one sale--recurly limitation)
|
||||||
# jamtracks however can be piled onto the same sale as adjustments (VRFS-3028)
|
# jamtracks however can be piled onto the same sale as adjustments (VRFS-3028)
|
||||||
# so this method may create 1 or more sales, , where 2 or more sales can occur if there are more than one subscriptions or subscription + jamtrack
|
# so this method may create 1 or more sales, , where 2 or more sales can occur if there are more than one subscriptions or subscription + jamtrack
|
||||||
def self.place_order(current_user, shopping_carts)
|
def self.place_order(current_user, shopping_carts, paypal = false)
|
||||||
|
|
||||||
sales = []
|
sales = []
|
||||||
|
|
||||||
|
|
@ -176,7 +177,7 @@ module JamRuby
|
||||||
# return sales
|
# return sales
|
||||||
#end
|
#end
|
||||||
|
|
||||||
jam_track_sale = order_jam_tracks(current_user, shopping_carts)
|
jam_track_sale = order_jam_tracks(current_user, shopping_carts, paypal)
|
||||||
sales << jam_track_sale if jam_track_sale
|
sales << jam_track_sale if jam_track_sale
|
||||||
|
|
||||||
# TODO: process shopping_carts_subscriptions
|
# TODO: process shopping_carts_subscriptions
|
||||||
|
|
@ -377,7 +378,7 @@ module JamRuby
|
||||||
|
|
||||||
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
|
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
|
||||||
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
|
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
|
||||||
def self.order_jam_tracks(current_user, shopping_carts)
|
def self.order_jam_tracks(current_user, shopping_carts, is_paypal)
|
||||||
|
|
||||||
shopping_carts_jam_tracks = []
|
shopping_carts_jam_tracks = []
|
||||||
shopping_carts_subscriptions = []
|
shopping_carts_subscriptions = []
|
||||||
|
|
@ -395,11 +396,9 @@ module JamRuby
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
client = RecurlyClient.new
|
|
||||||
|
|
||||||
sale = nil
|
sale = nil
|
||||||
Sale.transaction do
|
Sale.transaction do
|
||||||
sale = create_jam_track_sale(current_user, SOURCE_RECURLY)
|
sale = create_jam_track_sale(current_user, is_paypal ? SOURCE_PAYPAL : SOURCE_RECURLY)
|
||||||
|
|
||||||
if sale.valid?
|
if sale.valid?
|
||||||
if is_only_freebie(shopping_carts)
|
if is_only_freebie(shopping_carts)
|
||||||
|
|
@ -429,61 +428,142 @@ module JamRuby
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
account = client.get_account(current_user)
|
if is_paypal
|
||||||
if account.present?
|
|
||||||
|
|
||||||
purge_pending_adjustments(account)
|
sale.process_shopping_carts(current_user, shopping_carts)
|
||||||
|
|
||||||
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
|
paypal_auth = current_user.paypal_auth
|
||||||
|
|
||||||
# now invoice the sale ... almost done
|
@api = PayPal::SDK::Merchant::API.new
|
||||||
|
@get_express_checkout_details = @api.build_get_express_checkout_details({:Token => paypal_auth.token})
|
||||||
|
@response = @api.get_express_checkout_details(@get_express_checkout_details)
|
||||||
|
|
||||||
begin
|
@@log.info("User #{current_user.email}, GetExpressCheckout: #{@response.inspect}")
|
||||||
invoice = account.invoice!
|
|
||||||
sale.recurly_invoice_id = invoice.uuid
|
tax = false
|
||||||
sale.recurly_invoice_number = invoice.invoice_number
|
if @response.Ack == 'Success'
|
||||||
|
payerInfo = @response.GetExpressCheckoutDetailsResponseDetails.PayerInfo
|
||||||
|
if payerInfo.Address && ( payerInfo.Address.Country == 'US' && payerInfo.Address.StateOrProvince == 'TX')
|
||||||
|
# we need to ask for taxes
|
||||||
|
tax = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tax_rate = tax ? 0.0825 : 0
|
||||||
|
total = current_user.shopping_cart_total.round(2)
|
||||||
|
tax_total = (total * tax_rate).round(2)
|
||||||
|
total = total + tax_total
|
||||||
|
total = total.round(2)
|
||||||
|
|
||||||
|
@do_express_checkout_payment = @api.build_do_express_checkout_payment({
|
||||||
|
:DoExpressCheckoutPaymentRequestDetails => {
|
||||||
|
:PaymentDetails =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
:OrderTotal => {
|
||||||
|
:currencyID => "USD",
|
||||||
|
:value => total
|
||||||
|
},
|
||||||
|
:PaymentAction => "Sale"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
:Token => paypal_auth.token,
|
||||||
|
:PayerID => paypal_auth.uid, }})
|
||||||
|
@pay_response = @api.do_express_checkout_payment(@do_express_checkout_payment)
|
||||||
|
|
||||||
|
@@log.info("User #{current_user.email}, DoExpressCheckoutPayment: #{@pay_response.inspect}")
|
||||||
|
|
||||||
|
# #<PayPal::SDK::Merchant::DataTypes::DoExpressCheckoutPaymentResponseType:0x007fe511dd9b88
|
||||||
|
# @Timestamp=Sun, 11 Dec 2016 02:09:31 +0000, @Ack="Success",
|
||||||
|
# @CorrelationID="b28faf6bd90d9", @Version="117.0", @Build="000000",
|
||||||
|
# @DoExpressCheckoutPaymentResponseDetails=#<PayPal::SDK::Merchant::DataTypes::DoExpressCheckoutPaymentResponseDetailsType:0x007fe511dd38c8
|
||||||
|
# @Token="EC-7A4606566T700564B",
|
||||||
|
# @PaymentInfo=[#<PayPal::SDK::Merchant::DataTypes::PaymentInfoType:0x007fe511dd3008 @TransactionID="63C410710F2619403", @ParentTransactionID=nil,
|
||||||
|
# @ReceiptID=nil, @TransactionType="express-checkout", @PaymentType="instant", @PaymentDate=Sun, 11 Dec 2016 02:09:31 +0000,
|
||||||
|
# @GrossAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dd0c90 @currencyID="USD", @value="1.99">,
|
||||||
|
# @FeeAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dd04e8 @currencyID="USD", @value="0.36">,
|
||||||
|
# @TaxAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dcbe98 @currencyID="USD", @value="0.00">,
|
||||||
|
# @ExchangeRate=nil, @PaymentStatus="Completed", @PendingReason="none", @ReasonCode="none", @ProtectionEligibility="Eligible",
|
||||||
|
# @ProtectionEligibilityType="ItemNotReceivedEligible,UnauthorizedPaymentEligible", @SellerDetails=#<PayPal::SDK::Merchant::DataTypes::SellerDetailsType:0x007fe511dcb358 @SecureMerchantAccountID="6MB486RSBRMJ2">>],
|
||||||
|
# @SuccessPageRedirectRequested="false", @CoupledPaymentInfo=[#<PayPal::SDK::Merchant::DataTypes::CoupledPaymentInfoType:0x007fe511dca7a0>]>>
|
||||||
|
|
||||||
|
if @pay_response.Ack == 'Success'
|
||||||
|
details = @pay_response.DoExpressCheckoutPaymentResponseDetails.PaymentInfo[0]
|
||||||
|
sale.recurly_invoice_id = details.TransactionID
|
||||||
|
sale.recurly_invoice_number = details.ReceiptID
|
||||||
|
|
||||||
# now slap in all the real tax/purchase totals
|
# now slap in all the real tax/purchase totals
|
||||||
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
|
sale.recurly_subtotal_in_cents = ((details.GrossAmount.value.to_f - details.TaxAmount.value.to_f) * 100).to_i
|
||||||
sale.recurly_tax_in_cents = invoice.tax_in_cents
|
sale.recurly_tax_in_cents = (details.TaxAmount.value.to_f * 100).to_i
|
||||||
sale.recurly_total_in_cents = invoice.total_in_cents
|
sale.recurly_total_in_cents = (details.GrossAmount.value.to_f * 100).to_i
|
||||||
sale.recurly_currency = invoice.currency
|
sale.recurly_currency = details.GrossAmount.currencyID
|
||||||
|
|
||||||
# and resolve against sale_line_items
|
|
||||||
sale.sale_line_items.each do |sale_line_item|
|
|
||||||
found_line_item = false
|
|
||||||
invoice.line_items.each do |line_item|
|
|
||||||
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
|
|
||||||
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
|
|
||||||
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
|
|
||||||
sale_line_item.recurly_currency = line_item.currency
|
|
||||||
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
|
|
||||||
found_line_item = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
if !found_line_item
|
|
||||||
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
|
|
||||||
puts "CANT FIND LINE ITEM"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless sale.save
|
unless sale.save
|
||||||
puts "WTF"
|
puts "Invalid sale (at end)."
|
||||||
raise RecurlyClientError, "Invalid sale (at end)."
|
raise PayPalClientError, "Invalid sale (at end)."
|
||||||
end
|
end
|
||||||
rescue Recurly::Resource::Invalid => e
|
else
|
||||||
# this exception is thrown by invoice! if the invoice is invalid
|
@@log.error("User #{current_user.email}, DoExpressCheckoutPayment: #{@pay_response.inspect}")
|
||||||
sale.rollback_adjustments(current_user, created_adjustments)
|
raise PayPalClientError, @pay_response.Errors[0].LongMessage
|
||||||
sale = nil
|
|
||||||
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
|
|
||||||
rescue => e
|
|
||||||
puts "UNKNOWN E #{e}"
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise RecurlyClientError, "Could not find account to place order."
|
client = RecurlyClient.new
|
||||||
|
account = client.get_account(current_user)
|
||||||
|
if account.present?
|
||||||
|
|
||||||
|
purge_pending_adjustments(account)
|
||||||
|
|
||||||
|
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
|
||||||
|
|
||||||
|
# now invoice the sale ... almost done
|
||||||
|
|
||||||
|
begin
|
||||||
|
invoice = account.invoice!
|
||||||
|
sale.recurly_invoice_id = invoice.uuid
|
||||||
|
sale.recurly_invoice_number = invoice.invoice_number
|
||||||
|
|
||||||
|
# now slap in all the real tax/purchase totals
|
||||||
|
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
|
||||||
|
sale.recurly_tax_in_cents = invoice.tax_in_cents
|
||||||
|
sale.recurly_total_in_cents = invoice.total_in_cents
|
||||||
|
sale.recurly_currency = invoice.currency
|
||||||
|
|
||||||
|
# and resolve against sale_line_items
|
||||||
|
sale.sale_line_items.each do |sale_line_item|
|
||||||
|
found_line_item = false
|
||||||
|
invoice.line_items.each do |line_item|
|
||||||
|
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
|
||||||
|
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
|
||||||
|
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
|
||||||
|
sale_line_item.recurly_currency = line_item.currency
|
||||||
|
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
|
||||||
|
found_line_item = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
if !found_line_item
|
||||||
|
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
|
||||||
|
puts "CANT FIND LINE ITEM"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless sale.save
|
||||||
|
puts "Invalid sale (at end)."
|
||||||
|
raise RecurlyClientError, "Invalid sale (at end)."
|
||||||
|
end
|
||||||
|
rescue Recurly::Resource::Invalid => e
|
||||||
|
# this exception is thrown by invoice! if the invoice is invalid
|
||||||
|
sale.rollback_adjustments(current_user, created_adjustments)
|
||||||
|
sale = nil
|
||||||
|
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
|
||||||
|
rescue => e
|
||||||
|
puts "UNKNOWN E #{e}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise RecurlyClientError, "Could not find account to place order."
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -493,7 +573,7 @@ module JamRuby
|
||||||
sale
|
sale
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_shopping_carts(current_user, shopping_carts, account)
|
def process_shopping_carts(current_user, shopping_carts, account = nil)
|
||||||
|
|
||||||
created_adjustments = []
|
created_adjustments = []
|
||||||
|
|
||||||
|
|
@ -515,7 +595,7 @@ module JamRuby
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def process_shopping_cart(current_user, shopping_cart, account, created_adjustments)
|
def process_shopping_cart(current_user, shopping_cart, recurly_account, created_adjustments)
|
||||||
recurly_adjustment_uuid = nil
|
recurly_adjustment_uuid = nil
|
||||||
recurly_adjustment_credit_uuid = nil
|
recurly_adjustment_credit_uuid = nil
|
||||||
|
|
||||||
|
|
@ -536,14 +616,14 @@ module JamRuby
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
if account
|
if recurly_account
|
||||||
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
|
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
|
||||||
adjustments = shopping_cart.create_adjustment_attributes(current_user)
|
adjustments = shopping_cart.create_adjustment_attributes(current_user)
|
||||||
|
|
||||||
adjustments.each do |adjustment|
|
adjustments.each do |adjustment|
|
||||||
|
|
||||||
# create the adjustment at Recurly (this may not look like it, but it is a REST API)
|
# create the adjustment at Recurly (this may not look like it, but it is a REST API)
|
||||||
created_adjustment = account.adjustments.new(adjustment)
|
created_adjustment = recurly_account.adjustments.new(adjustment)
|
||||||
created_adjustment.save
|
created_adjustment.save
|
||||||
|
|
||||||
# if the adjustment could not be made, bail
|
# if the adjustment could not be made, bail
|
||||||
|
|
|
||||||
|
|
@ -1912,6 +1912,13 @@ module JamRuby
|
||||||
stats
|
stats
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def shopping_cart_total
|
||||||
|
total = 0
|
||||||
|
shopping_carts.each do |shopping_cart|
|
||||||
|
total += shopping_cart.product_info[:total_price]
|
||||||
|
end
|
||||||
|
total
|
||||||
|
end
|
||||||
def destroy_all_shopping_carts
|
def destroy_all_shopping_carts
|
||||||
ShoppingCart.where("user_id=?", self).destroy_all
|
ShoppingCart.where("user_id=?", self).destroy_all
|
||||||
end
|
end
|
||||||
|
|
@ -2053,6 +2060,15 @@ module JamRuby
|
||||||
user_authorizations.where(provider: "stripe_connect").first
|
user_authorizations.where(provider: "stripe_connect").first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def paypal_auth
|
||||||
|
user_authorizations.where(provider: 'paypal').first
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_paypal_auth?
|
||||||
|
auth = paypal_auth
|
||||||
|
auth && (!auth.token_expiration || auth.token_expiration > Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
def has_stripe_connect?
|
def has_stripe_connect?
|
||||||
auth = stripe_auth
|
auth = stripe_auth
|
||||||
auth && (!auth.token_expiration || auth.token_expiration > Time.now)
|
auth && (!auth.token_expiration || auth.token_expiration > Time.now)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ gem 'sprockets-rails', '2.3.2'
|
||||||
gem 'non-stupid-digest-assets'
|
gem 'non-stupid-digest-assets'
|
||||||
#gem 'license_finder'
|
#gem 'license_finder'
|
||||||
gem 'pg_migrate', '0.1.14'
|
gem 'pg_migrate', '0.1.14'
|
||||||
|
#gem 'paypal-sdk-rest'
|
||||||
|
gem 'paypal-sdk-merchant', github: 'sylv3rblade/merchant-sdk-ruby'
|
||||||
gem 'kickbox'
|
gem 'kickbox'
|
||||||
gem 'oj', '2.10.2'
|
gem 'oj', '2.10.2'
|
||||||
gem 'builder'
|
gem 'builder'
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
//= require jquery.Jcrop
|
//= require jquery.Jcrop
|
||||||
//= require jquery.naturalsize
|
//= require jquery.naturalsize
|
||||||
//= require jquery.queryparams
|
//= require jquery.queryparams
|
||||||
//= require jquery.clipboard
|
//= require clipboard
|
||||||
//= require jquery.timeago
|
//= require jquery.timeago
|
||||||
//= require jquery.easydropdown
|
//= require jquery.easydropdown
|
||||||
//= require jquery.scrollTo
|
//= require jquery.scrollTo
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
var userDetail = null;
|
var userDetail = null;
|
||||||
var entity = null;
|
var entity = null;
|
||||||
var remainingCap = 140 - 22 - 1; // 140 tweet max, minus 22 for link size, minus 1 for space
|
var remainingCap = 140 - 22 - 1; // 140 tweet max, minus 22 for link size, minus 1 for space
|
||||||
|
var clipboard = null;
|
||||||
|
|
||||||
function showSpinner() {
|
function showSpinner() {
|
||||||
$(dialogId + ' .dialog-inner').hide();
|
$(dialogId + ' .dialog-inner').hide();
|
||||||
|
|
@ -444,27 +445,6 @@
|
||||||
|
|
||||||
function afterShow() {
|
function afterShow() {
|
||||||
$("#shareType").text(entityType);
|
$("#shareType").text(entityType);
|
||||||
if(context.JK.hasFlash()) {
|
|
||||||
$("#btn-share-copy").clipboard({
|
|
||||||
path: '/assets/jquery.clipboard.swf',
|
|
||||||
copy: function() {
|
|
||||||
// Return text in closest element (useful when you have multiple boxes that can be copied)
|
|
||||||
return $(".link-contents").text();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(context.jamClient) {
|
|
||||||
// uses bridge call to ultimately access QClipboard
|
|
||||||
$("#btn-share-copy").unbind('click').click(function() {
|
|
||||||
context.jamClient.SaveToClipboard($(".link-contents").text());
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.debug("no copy-to-clipboard capabilities")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function afterHide() {
|
function afterHide() {
|
||||||
|
|
@ -486,6 +466,20 @@
|
||||||
//initDialog();
|
//initDialog();
|
||||||
|
|
||||||
facebookHelper.deferredLoginStatus().done(function(response) { handleFbStateChange(response); });
|
facebookHelper.deferredLoginStatus().done(function(response) { handleFbStateChange(response); });
|
||||||
|
|
||||||
|
if(context.jamClient.IsNativeClient()) {
|
||||||
|
$("#btn-share-copy").unbind('click').click(function() {
|
||||||
|
context.jamClient.SaveToClipboard($("#link-contents").text());
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clipboard = new Clipboard('#btn-share-copy', {
|
||||||
|
text: function(trigger) {
|
||||||
|
return $("#link-contents").text();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initialize = initialize;
|
this.initialize = initialize;
|
||||||
|
|
|
||||||
|
|
@ -2734,6 +2734,27 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function paypalDetail(options) {
|
||||||
|
options = options || {}
|
||||||
|
return $.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/api/paypal/checkout/detail',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(options)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function paypalPlaceOrder(options) {
|
||||||
|
options = options || {}
|
||||||
|
return $.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/api/paypal/checkout/confirm',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(options)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
return self;
|
return self;
|
||||||
|
|
@ -2977,6 +2998,8 @@
|
||||||
this.posaActivate = posaActivate;
|
this.posaActivate = posaActivate;
|
||||||
this.posaClaim = posaClaim;
|
this.posaClaim = posaClaim;
|
||||||
this.sendRetailerCustomerEmail = sendRetailerCustomerEmail;
|
this.sendRetailerCustomerEmail = sendRetailerCustomerEmail;
|
||||||
|
this.paypalDetail = paypalDetail;
|
||||||
|
this.paypalPlaceOrder = paypalPlaceOrder;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
})(window,jQuery);
|
})(window,jQuery);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
//= require jquery.queryparams
|
//= require jquery.queryparams
|
||||||
//= require jquery.hoverIntent
|
//= require jquery.hoverIntent
|
||||||
//= require jquery.cookie
|
//= require jquery.cookie
|
||||||
//= require jquery.clipboard
|
//= require clipboard
|
||||||
//= require jquery.easydropdown
|
//= require jquery.easydropdown
|
||||||
//= require jquery.carousel-1.1
|
//= require jquery.carousel-1.1
|
||||||
//= require jquery.mousewheel-3.1.9
|
//= require jquery.mousewheel-3.1.9
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ MIX_MODES = context.JK.MIX_MODES
|
||||||
.done((response) =>
|
.done((response) =>
|
||||||
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
|
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
|
||||||
)
|
)
|
||||||
.fail(() =>
|
.fail((jqXHR) =>
|
||||||
@app.notifyServerError jqXHR, 'Search Unavailable'
|
@app.notifyServerError jqXHR, 'Search Unavailable'
|
||||||
@setState({searching: false, first_search: false})
|
@setState({searching: false, first_search: false})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -326,12 +326,12 @@ MIX_MODES = context.JK.MIX_MODES
|
||||||
.done((response) =>
|
.done((response) =>
|
||||||
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
|
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
|
||||||
)
|
)
|
||||||
.fail(() =>
|
.fail((jqXHR) =>
|
||||||
@app.notifyServerError jqXHR, 'Search Unavailable'
|
@app.notifyServerError jqXHR, 'Search Unavailable'
|
||||||
@setState({searching: false, first_search: false})
|
@setState({searching: false, first_search: false})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.fail(() =>
|
.fail((jqXHR) =>
|
||||||
@app.notifyServerError jqXHR, 'Search Unavailable'
|
@app.notifyServerError jqXHR, 'Search Unavailable'
|
||||||
@setState({searching: false, first_search: false})
|
@setState({searching: false, first_search: false})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
context = window
|
||||||
|
MIX_MODES = context.JK.MIX_MODES
|
||||||
|
|
||||||
|
@PayPalConfirmationScreen = React.createClass({
|
||||||
|
|
||||||
|
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
|
||||||
|
|
||||||
|
render: () ->
|
||||||
|
content = null
|
||||||
|
|
||||||
|
|
||||||
|
if this.state.sold
|
||||||
|
|
||||||
|
if context.jamClient && context.jamClient.IsNativeClient()
|
||||||
|
platformMessage = `<div>
|
||||||
|
<p> To play your purchased JamTrack, start a session and then open the JamTrack</p>
|
||||||
|
<a className="download-jamkazam-wrapper" href="/client#/createSession">
|
||||||
|
<div className="download-jamkazam">
|
||||||
|
Click Here to Start a Session
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>`
|
||||||
|
else
|
||||||
|
platformMessage =
|
||||||
|
`<div>
|
||||||
|
<a href='/client#/jamtrack' className="download-jamkazam-wrapper jt-popup">
|
||||||
|
<div className="download-jamkazam">
|
||||||
|
Click Here to Start Using Your JamTrack
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href='/downloads' rel="external" className="download-jamkazam-wrapper" target="_blank">
|
||||||
|
<div>
|
||||||
|
Do More With Your JamTrack - Click Here to Download Our Application
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a className="back-to-browsing" href="/client#/jamtrack">
|
||||||
|
or click here to browse more jamtracks
|
||||||
|
</a>
|
||||||
|
</div>`
|
||||||
|
content = `<div className="sold-notice">
|
||||||
|
<h2>Thank you for your order!</h2>
|
||||||
|
{platformMessage}
|
||||||
|
</div>`
|
||||||
|
else
|
||||||
|
orderButtons = {"button-orange": true, "place-order-btn": true, disabled: this.state.ordering }
|
||||||
|
cancelButtons = {"button-grey": true, "cancel": true, disabled: this.state.ordering }
|
||||||
|
|
||||||
|
content = `<div>
|
||||||
|
<h2 className="confirm-header">Confirm PayPal Payment</h2>
|
||||||
|
|
||||||
|
<p>You have not yet made a payment via PayPal. Please review your purchase and confirm or cancel.</p>
|
||||||
|
|
||||||
|
<div className="controls">
|
||||||
|
<a href="#" className={classNames(orderButtons)} onClick={this.placeOrder}>CONFIRM PURCHASE WITH
|
||||||
|
PAYPAL</a>
|
||||||
|
<a href="#" className={classNames(cancelButtons)} onClick={this.cancel}>CANCEL</a>
|
||||||
|
|
||||||
|
<div className="clearall"/>
|
||||||
|
</div>
|
||||||
|
<ShoppingCartContents carts={this.state.carts}/>
|
||||||
|
|
||||||
|
<div className="controls bottom">
|
||||||
|
<a href="#" className={classNames(orderButtons)} onClick={this.placeOrder}>CONFIRM PURCHASE WITH
|
||||||
|
PAYPAL</a>
|
||||||
|
<a href="#" className={classNames(cancelButtons)} onClick={this.cancel}>CANCEL</a>
|
||||||
|
|
||||||
|
<div className="clearall"/>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
`<div className="PayPalConfirmationScreen">
|
||||||
|
<div className="content-body-scroller">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
placeOrder: (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
if this.state.ordering
|
||||||
|
return
|
||||||
|
@setState({ordering: true})
|
||||||
|
|
||||||
|
console.log("placing order with paypal")
|
||||||
|
@rest.paypalPlaceOrder()
|
||||||
|
.done((response) =>
|
||||||
|
console.log("paypal detail obtained", response)
|
||||||
|
|
||||||
|
@setState({sold: true, ordering: false})
|
||||||
|
context.JK.JamTrackUtils.checkShoppingCart()
|
||||||
|
@app.refreshUser()
|
||||||
|
)
|
||||||
|
.fail((jqXHR) =>
|
||||||
|
@setState({ordering: false})
|
||||||
|
if jqXHR.status == 404
|
||||||
|
context.JK.Banner.showAlert('PayPal Session Over', 'Your PayPal authorization has expired. Please restart the PayPal confirmation process. <a href="/client#/checkoutPayment">Click Here to Checkout Again.</a>')
|
||||||
|
else if jqXHR.status == 422
|
||||||
|
response = JSON.parse(jqXHR.responseText)
|
||||||
|
context.JK.Banner.showAlert('PayPal Purchase Error', 'PayPal: ' + response.message)
|
||||||
|
else
|
||||||
|
context.JK.Banner.showAlert('PayPal/Sales Error', 'Please contact support@jamkazam.com')
|
||||||
|
)
|
||||||
|
|
||||||
|
cancelOrder: (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
window.location = '/client#/jamtrack'
|
||||||
|
|
||||||
|
getInitialState: () ->
|
||||||
|
{}
|
||||||
|
|
||||||
|
componentDidMount: () ->
|
||||||
|
|
||||||
|
componentDidUpdate: () ->
|
||||||
|
|
||||||
|
afterShow: (data) ->
|
||||||
|
rest.getShoppingCarts()
|
||||||
|
.done((carts) =>
|
||||||
|
@setState({carts: carts})
|
||||||
|
if carts.length == 0
|
||||||
|
window.location = '/client#/jamtrack'
|
||||||
|
return
|
||||||
|
|
||||||
|
@rest.paypalDetail()
|
||||||
|
.done((response) =>
|
||||||
|
console.log("paypal detail obtained", response)
|
||||||
|
)
|
||||||
|
.fail((jqXHR) =>
|
||||||
|
if jqXHR.status == 404
|
||||||
|
context.JK.Banner.showAlert('PayPal Session Over', 'Your PayPal authorization has expired. Please restart the PayPal confirmation process. <a href="/client#/checkoutPayment">Click Here to Checkout Again.</a>')
|
||||||
|
else if jqXHR.status == 422
|
||||||
|
response = JSON.parse(jqXHR.responseText)
|
||||||
|
context.JK.Banner.showAlert('PayPal Purchase Error', 'PayPal: ' + response.message)
|
||||||
|
else
|
||||||
|
context.JK.Banner.showAlert('PayPal/Sales Error', 'Please contact support@jamkazam.com')
|
||||||
|
@app.notifyServerError jqXHR, 'PayPal Communication Error'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.fail((jqXHR) =>
|
||||||
|
@app.notifyServerError jqXHR, 'Unable to fetch carts'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
beforeShow: () ->
|
||||||
|
this.setState({sold: false})
|
||||||
|
|
||||||
|
onAppInit: (@app) ->
|
||||||
|
@EVENTS = context.JK.EVENTS
|
||||||
|
@rest = context.JK.Rest()
|
||||||
|
@logger = context.JK.logger
|
||||||
|
|
||||||
|
screenBindings =
|
||||||
|
'beforeShow': @beforeShow
|
||||||
|
'afterShow': @afterShow
|
||||||
|
|
||||||
|
@app.bindScreen('paypal/confirm', screenBindings)
|
||||||
|
|
||||||
|
onUserChanged: (userState) ->
|
||||||
|
@user = userState?.user
|
||||||
|
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
context = window
|
||||||
|
MIX_MODES = context.JK.MIX_MODES
|
||||||
|
|
||||||
|
@ShoppingCartContents = React.createClass({
|
||||||
|
|
||||||
|
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
|
||||||
|
|
||||||
|
render: () ->
|
||||||
|
|
||||||
|
carts = []
|
||||||
|
|
||||||
|
if this.props.carts?
|
||||||
|
if this.props.carts.length == 0
|
||||||
|
carts = `<div className="no-cart-items">You have nothing in your cart</div>`
|
||||||
|
else
|
||||||
|
taxRate = 0
|
||||||
|
if this.props.tax
|
||||||
|
taxRate = 0.0825
|
||||||
|
|
||||||
|
estimatedTax = 0
|
||||||
|
estimatedTotal = 0
|
||||||
|
|
||||||
|
for cart in this.props.carts
|
||||||
|
cart_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
|
||||||
|
estimatedTax += cart.product_info.price * cart_quantity * taxRate
|
||||||
|
estimatedTotal += cart.product_info.price * cart_quantity
|
||||||
|
|
||||||
|
estimatedTax = Math.round(estimatedTax * 100) / 100
|
||||||
|
estimatedTotal = Math.round((estimatedTotal + estimatedTax) * 100) / 100
|
||||||
|
|
||||||
|
for cart in this.props.carts
|
||||||
|
console.log("CART", cart)
|
||||||
|
freeNotice = null
|
||||||
|
if cart.product_info.free
|
||||||
|
freeNotice = `<span className="first-one-free">| (first one free)</span>`
|
||||||
|
carts.push(`<div className="cart-item" key={cart.id}>
|
||||||
|
<div className="cart-item-caption">
|
||||||
|
<span>{cart.product_info.sale_display}</span>
|
||||||
|
{freeNotice}
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-price">
|
||||||
|
$ {Number(cart.product_info.real_price).toFixed(2)}
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-quantity">
|
||||||
|
{cart.quantity}
|
||||||
|
</div>
|
||||||
|
<div className="clearall"/>
|
||||||
|
</div>`)
|
||||||
|
|
||||||
|
carts.push(`<div className="cart-item tax-total" key={'tax'}>
|
||||||
|
<div className="cart-item-caption">
|
||||||
|
<span>Tax</span>
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-price">
|
||||||
|
$ {estimatedTax.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-quantity">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="clearall"/>
|
||||||
|
</div>`)
|
||||||
|
|
||||||
|
carts.push(`<div className="cart-item total" key={'total'}>
|
||||||
|
<div className="cart-item-caption">
|
||||||
|
<span>Total</span>
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-price">
|
||||||
|
$ {estimatedTotal.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-quantity">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="clearall"/>
|
||||||
|
</div>`)
|
||||||
|
else
|
||||||
|
carts = `<div className="loading-indicator">Loading...</div>`
|
||||||
|
|
||||||
|
`<div className="shopping-cart-contents">
|
||||||
|
<div className="order-items-page">
|
||||||
|
<div className="cart-items">
|
||||||
|
<div className="cart-item-caption">
|
||||||
|
<span>YOUR ORDER INCLUDES:</span>
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-price">
|
||||||
|
<span>PRICE</span>
|
||||||
|
</div>
|
||||||
|
<div className="cart-item-quantity">
|
||||||
|
<span>QUANTITY</span>
|
||||||
|
</div>
|
||||||
|
<div className="clearall"></div>
|
||||||
|
{carts}
|
||||||
|
<div className="clearall"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
onAppInit: (@app) ->
|
||||||
|
@EVENTS = context.JK.EVENTS
|
||||||
|
@rest = context.JK.Rest()
|
||||||
|
@logger = context.JK.logger
|
||||||
|
|
||||||
|
|
||||||
|
onUserChanged: (userState) ->
|
||||||
|
@user = userState?.user
|
||||||
|
|
||||||
|
})
|
||||||
|
|
@ -43,6 +43,7 @@ proficiencyDescriptionMap = {
|
||||||
TILE_RATINGS: 'ratings'
|
TILE_RATINGS: 'ratings'
|
||||||
TILE_PRICES: 'prices'
|
TILE_PRICES: 'prices'
|
||||||
visible: false
|
visible: false
|
||||||
|
profileClipboard: null
|
||||||
|
|
||||||
TILES: ['about', 'experience', 'samples', 'ratings', 'prices']
|
TILES: ['about', 'experience', 'samples', 'ratings', 'prices']
|
||||||
|
|
||||||
|
|
@ -65,11 +66,26 @@ proficiencyDescriptionMap = {
|
||||||
@root = $(@getDOMNode())
|
@root = $(@getDOMNode())
|
||||||
@screen = $('#teacher-profile')
|
@screen = $('#teacher-profile')
|
||||||
@starbox()
|
@starbox()
|
||||||
|
@clipboard()
|
||||||
|
|
||||||
componentDidUpdate:() ->
|
componentDidUpdate:() ->
|
||||||
@starbox()
|
@starbox()
|
||||||
|
|
||||||
context.JK.popExternalLinks(@root)
|
context.JK.popExternalLinks(@root)
|
||||||
|
@clipboard()
|
||||||
|
|
||||||
|
clipboard: () ->
|
||||||
|
$profileLink = @root.find('.copy-profile-link')
|
||||||
|
|
||||||
|
if $profileLink.length > 0 && !@profileClipboard?
|
||||||
|
# mount it
|
||||||
|
@profileClipboard = new Clipboard($profileLink.get(0), {
|
||||||
|
text: =>
|
||||||
|
return context.JK.makeAbsolute('/client#/teacher/profile/' + @state.user.teacher?.id)
|
||||||
|
})
|
||||||
|
else if $profileLink.length == 0 && @profileClipboard?
|
||||||
|
@profileClipboard.destroy()
|
||||||
|
@profileClipboard = null
|
||||||
|
|
||||||
starbox:() ->
|
starbox:() ->
|
||||||
$ratings = @root.find('.ratings-box')
|
$ratings = @root.find('.ratings-box')
|
||||||
|
|
@ -230,7 +246,9 @@ proficiencyDescriptionMap = {
|
||||||
biography = biography.replace(/\n/g, "<br/>")
|
biography = biography.replace(/\n/g, "<br/>")
|
||||||
|
|
||||||
`<div className="section bio">
|
`<div className="section bio">
|
||||||
<h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
|
<a className="copy-profile-link button-orange" href='#' onClick={this.copyProfileLink}>
|
||||||
|
COPY PROFILE URL TO CLIPBOARD
|
||||||
|
</a><h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
|
||||||
<div className="section-content">
|
<div className="section-content">
|
||||||
<div dangerouslySetInnerHTML={{__html: biography}}></div>
|
<div dangerouslySetInnerHTML={{__html: biography}}></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -694,6 +712,16 @@ proficiencyDescriptionMap = {
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
|
copyProfileLink: (e) ->
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
@app.layout.notify({
|
||||||
|
title: 'Teacher Profile Link Copied',
|
||||||
|
text: "Your clipboard now has a link to this teacher that you can share with anyone."
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
selectionMade: (selection, e) ->
|
selectionMade: (selection, e) ->
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false }
|
var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false }
|
||||||
$self.triggerHandler('startedRecording', details);
|
$self.triggerHandler('startedRecording', details);
|
||||||
currentlyRecording = false;
|
currentlyRecording = false;
|
||||||
context.RecordingActions.startedRecording(details);
|
context.RecordingActions.startedRecording(details);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
//= require jquery.queryparams
|
//= require jquery.queryparams
|
||||||
//= require jquery.hoverIntent
|
//= require jquery.hoverIntent
|
||||||
//= require jquery.cookie
|
//= require jquery.cookie
|
||||||
//= require jquery.clipboard
|
//= require clipboard
|
||||||
//= require jquery.easydropdown
|
//= require jquery.easydropdown
|
||||||
//= require jquery.carousel-1.1
|
//= require jquery.carousel-1.1
|
||||||
//= require jquery.mousewheel-3.1.9
|
//= require jquery.mousewheel-3.1.9
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paypal-region {
|
||||||
|
text-align: center;
|
||||||
|
margin:10px auto 0;
|
||||||
|
/**margin: 10px auto 0;
|
||||||
|
padding: 10px 10px 5px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-color: #ccc;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 3px;
|
||||||
|
width:145px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-text {
|
||||||
|
margin: 60px auto 0;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color:white;
|
color:white;
|
||||||
background-color:#4d4d4d;
|
background-color:#4d4d4d;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
@import "client/common.scss";
|
||||||
|
|
||||||
|
[data-react-class="PayPalConfirmationScreen"] {
|
||||||
|
height: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
.content-body-scroller {
|
||||||
|
height: calc(100% - 30px) ! important; // 15px top and bottom padding, and 48px used by .controls
|
||||||
|
padding: 15px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-header {
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls.bottom {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.place-order-btn {
|
||||||
|
text-align: center;
|
||||||
|
margin-right:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-holder {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-order-btn {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.shopping-cart-contents {
|
||||||
|
@include border-box_sizing;
|
||||||
|
width: 50%;
|
||||||
|
margin-top:20px;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
@include border-box_sizing;
|
||||||
|
width:50%;
|
||||||
|
a {
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading-indicator {
|
||||||
|
margin-bottom:20px;
|
||||||
|
padding-bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sold-notice {
|
||||||
|
h2 {
|
||||||
|
font-size:30px;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.download-jamkazam {
|
||||||
|
color:$ColorLink;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-style:solid;
|
||||||
|
border-color:#AAA;
|
||||||
|
border-width:1px;
|
||||||
|
padding:10px;
|
||||||
|
margin-top:20px;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-jamkazam-wrapper, .back-to-browsing {
|
||||||
|
text-align:center;
|
||||||
|
display:block;
|
||||||
|
margin-top:35px;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
@import "client/common.scss";
|
||||||
|
|
||||||
|
.shopping-cart-contents {
|
||||||
|
|
||||||
|
background-color:#262626;
|
||||||
|
border-width:0 1px 0 0;
|
||||||
|
border-style:solid;
|
||||||
|
border-color:#333;
|
||||||
|
padding:20px 20px 0;
|
||||||
|
.cart-item-caption {
|
||||||
|
width: 50%;
|
||||||
|
text-align: left;
|
||||||
|
float: left;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
@include border_box_sizing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-one-free {
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: italic;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-item-price {
|
||||||
|
width: 25%;
|
||||||
|
text-align: right;
|
||||||
|
float: left;
|
||||||
|
padding: 0 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
@include border_box_sizing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-item-quantity {
|
||||||
|
width: 10%;
|
||||||
|
text-align: right;
|
||||||
|
float: left;
|
||||||
|
padding: 0 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
@include border_box_sizing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-items {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-item {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-cart-items {
|
||||||
|
}
|
||||||
|
|
||||||
|
.tax-total {
|
||||||
|
margin-top:10px;
|
||||||
|
border-width:1px 0 0 0;
|
||||||
|
border-color:white;
|
||||||
|
border-style:solid;
|
||||||
|
padding-top:10px;
|
||||||
|
}
|
||||||
|
.cart-item.total {
|
||||||
|
margin-top:5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -199,6 +199,9 @@
|
||||||
position:absolute;
|
position:absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-profile-link {
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
|
||||||
.spinner-large {
|
.spinner-large {
|
||||||
width:200px;
|
width:200px;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ class ApiLessonSessionsController < ApiController
|
||||||
render "api_lesson_sessions/index", :layout => nil
|
render "api_lesson_sessions/index", :layout => nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
@ -72,13 +71,13 @@ class ApiLessonSessionsController < ApiController
|
||||||
|
|
||||||
if params[:update_all]
|
if params[:update_all]
|
||||||
# check if the next scheduled lesson is doable
|
# check if the next scheduled lesson is doable
|
||||||
if 24.hours.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
|
if 15.minutes.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
|
||||||
response = {message: 'time_limit'}
|
response = {message: 'time_limit'}
|
||||||
render :json => response, :status => 422
|
render :json => response, :status => 422
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if 24.hours.from_now > @lesson_session.music_session.scheduled_start
|
if 15.minutes.from_now > @lesson_session.music_session.scheduled_start
|
||||||
response = {message: 'time_limit'}
|
response = {message: 'time_limit'}
|
||||||
render :json => response, :status => 422
|
render :json => response, :status => 422
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
class ApiPayPalController < ApiController
|
||||||
|
|
||||||
|
before_filter :api_signed_in_user
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
|
||||||
|
def log
|
||||||
|
@log || Logging.logger[VanillaForumsController]
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_checkout
|
||||||
|
cancel_path = params[:path] ? params[:path] : ERB::Util.url_encode('/client#/checkoutPayment')
|
||||||
|
|
||||||
|
tax = true
|
||||||
|
tax_rate = tax ? 0.0825 : 0
|
||||||
|
total = current_user.shopping_cart_total.round(2)
|
||||||
|
tax_total = (total * tax_rate).round(2)
|
||||||
|
total = total + tax_total
|
||||||
|
total = total.round(2)
|
||||||
|
|
||||||
|
|
||||||
|
@api = PayPal::SDK::Merchant::API.new
|
||||||
|
@set_express_checkout = @api.build_set_express_checkout(
|
||||||
|
{
|
||||||
|
:Version => "117.0",
|
||||||
|
:SetExpressCheckoutRequestDetails =>
|
||||||
|
{
|
||||||
|
:ReturnURL => ApplicationHelper.base_uri(request) + '/auth/paypal/checkout',
|
||||||
|
:CancelURL => ApplicationHelper.base_uri(request) + '/auth/paypal/checkout?cancel=1&path=' + cancel_path,
|
||||||
|
# :NoShipping => "1",
|
||||||
|
# :ReqConfirmShipping => "0",
|
||||||
|
# :ReqBillingAddress => "1",
|
||||||
|
:PaymentDetails =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
:OrderTotal => {
|
||||||
|
:currencyID => "USD",
|
||||||
|
:value => total
|
||||||
|
},
|
||||||
|
:PaymentAction => "Sale"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@set_express_checkout_response = @api.set_express_checkout(@set_express_checkout)
|
||||||
|
|
||||||
|
log.info("User #{current_user.email}, SetExpressCheckout #{@set_express_checkout_response.inspect}")
|
||||||
|
|
||||||
|
if @set_express_checkout_response.Ack == 'Failure'
|
||||||
|
render json: {message: @set_express_checkout_response.Errors[0].LongMessage}, status: 422
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to Rails.configuration.paypal_express_url + '&token=' + ERB::Util.url_encode(@set_express_checkout_response.Token)
|
||||||
|
end
|
||||||
|
|
||||||
|
# called by frontend after the user comes back from initial express page
|
||||||
|
def checkout_detail
|
||||||
|
# here we can see if they will pay tax
|
||||||
|
|
||||||
|
if !current_user.has_paypal_auth?
|
||||||
|
render json: {}, :status => 404
|
||||||
|
return
|
||||||
|
end
|
||||||
|
paypal_auth = current_user.paypal_auth
|
||||||
|
|
||||||
|
@api = PayPal::SDK::Merchant::API.new
|
||||||
|
@get_express_checkout_details = @api.build_get_express_checkout_details({:Token => paypal_auth.token})
|
||||||
|
@response = @api.get_express_checkout_details(@get_express_checkout_details)
|
||||||
|
|
||||||
|
puts @response.inspect
|
||||||
|
tax = false
|
||||||
|
if @response.Ack == 'Success'
|
||||||
|
payerInfo = @response.GetExpressCheckoutDetailsResponseDetails.PayerInfo
|
||||||
|
if payerInfo.Address && ( payerInfo.Address.Country == 'US' && payerInfo.Address.StateOrProvince == 'TX')
|
||||||
|
# we need to ask for taxes
|
||||||
|
tax = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
render json: {message: @response.Errors[0].LongMessage}, status: 422
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
log.debug("User #{current_user.email}, GetExpressCheckout: #{@get_express_checkout_details_response.inspect}")
|
||||||
|
|
||||||
|
render json: {tax: tax}
|
||||||
|
end
|
||||||
|
|
||||||
|
# called by frontend when the user selects finally 'confirm purchase' (PLACE ORDER btn)
|
||||||
|
def confirm_purchase
|
||||||
|
if !current_user.has_paypal_auth?
|
||||||
|
render json: {}, :status => 404
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
error = nil
|
||||||
|
response = {jam_tracks: [], gift_cards: []}
|
||||||
|
|
||||||
|
#if Sale.is_mixed(current_user.shopping_carts)
|
||||||
|
# msg = "has free and non-free items. Try removing non-free items."
|
||||||
|
# render json: {message: "Cart " + msg, errors: {cart: [msg]}}, :status => 404
|
||||||
|
# return
|
||||||
|
#end
|
||||||
|
|
||||||
|
begin
|
||||||
|
sales = Sale.place_order(current_user, current_user.shopping_carts, true)
|
||||||
|
rescue RecurlyClientError => e
|
||||||
|
render json: {message: e.errors[:message]}, :status => 422
|
||||||
|
return
|
||||||
|
rescue PayPalClientError => x
|
||||||
|
render json: {message: x.errors[:message]}, :status => 422
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
sales.each do |sale|
|
||||||
|
sale.sale_line_items.each do |line_item|
|
||||||
|
if line_item.is_jam_track?
|
||||||
|
jam_track = line_item.product
|
||||||
|
jam_track_right = jam_track.right_for_user(current_user)
|
||||||
|
response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
|
||||||
|
elsif line_item.is_gift_card?
|
||||||
|
gift_card = line_item.product
|
||||||
|
response[:gift_cards] << {name: gift_card.name, id: gift_card.id}
|
||||||
|
else
|
||||||
|
raise 'unknown sale line item type: ' + line_item.product_type
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set_purchased_jamtrack_cookie
|
||||||
|
render :json => response, :status => 200
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# this is not a jam session - this is an 'auth session'
|
# this is not a jam session - this is an 'auth session'
|
||||||
class SessionsController < ApplicationController
|
class SessionsController < ApplicationController
|
||||||
|
|
||||||
|
before_filter :api_signed_in_user, only: :paypal_express_checkout
|
||||||
|
|
||||||
layout "web"
|
layout "web"
|
||||||
|
|
||||||
def signin
|
def signin
|
||||||
|
|
@ -37,6 +39,42 @@ class SessionsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def paypal_express_checkout
|
||||||
|
# should get 'token' and 'PayerID' on success
|
||||||
|
|
||||||
|
# on failure, cancel=1
|
||||||
|
|
||||||
|
if params[:cancel] == '1' || params[:cancel] == 1
|
||||||
|
redirect_to params[:path] ? params[:path] : '/client#/jamtrack'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
authorization = current_user.paypal_auth
|
||||||
|
|
||||||
|
# Always make and save a new authorization. This is because they expire, and honestly there's no cost
|
||||||
|
# to just making and saving it.
|
||||||
|
|
||||||
|
user_auth_hash = {
|
||||||
|
:provider => 'paypal',
|
||||||
|
:uid => params[:PayerID],
|
||||||
|
:token => params[:token],
|
||||||
|
:refresh_token => nil,
|
||||||
|
:token_expiration => 3.hours.from_now, # according to paypal docs, a token is good for 3 hours
|
||||||
|
:secret => nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if authorization.nil?
|
||||||
|
authorization = current_user.user_authorizations.build(user_auth_hash)
|
||||||
|
authorization.save
|
||||||
|
else
|
||||||
|
authorization.token = user_auth_hash[:token]
|
||||||
|
authorization.token_expiration = user_auth_hash[:token_expiration]
|
||||||
|
authorization.uid = user_auth_hash[:uid]
|
||||||
|
authorization.save
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to '/client#/paypal/confirm'
|
||||||
|
end
|
||||||
|
|
||||||
# OAuth docs
|
# OAuth docs
|
||||||
# http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/
|
# http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,13 @@ div layout="screen" layout-id="checkoutPayment" id="checkoutPaymentScreen" class
|
||||||
.divSaveCardHelper
|
.divSaveCardHelper
|
||||||
label for="save-card" Save card for future use
|
label for="save-card" Save card for future use
|
||||||
.clearall
|
.clearall
|
||||||
|
|
||||||
|
- if !Rails.application.config.paypal_admin_only || any_user.admin
|
||||||
|
.or-text or instead use:
|
||||||
|
.paypal-region
|
||||||
|
a href="/paypal/checkout/start" data-paypal-button="true"
|
||||||
|
img src="https://www.paypalobjects.com/webstatic/en_US/i/btn/png/gold-pill-paypalcheckout-34px.png" alt="PayPal Checkout"
|
||||||
|
a
|
||||||
.clearall
|
.clearall
|
||||||
.clearall
|
.clearall
|
||||||
.row.second
|
.row.second
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
.screen.secondary layout='screen' layout-id='paypal/confirm'
|
||||||
|
.content
|
||||||
|
.content-head
|
||||||
|
.content-icon=image_tag("content/icon_jamtracks.png", height: 19, width: 19)
|
||||||
|
h1 confirm payment
|
||||||
|
= render "screen_navigation"
|
||||||
|
.content-body
|
||||||
|
= react_component 'PayPalConfirmationScreen', {}
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
<%= render "jamtrack_search" %>
|
<%= render "jamtrack_search" %>
|
||||||
<%= render "jamtrack_filter" %>
|
<%= render "jamtrack_filter" %>
|
||||||
<%= render "jamtrack_landing" %>
|
<%= render "jamtrack_landing" %>
|
||||||
|
<%= render "paypal_confirmation" %>
|
||||||
<%= render "shopping_cart" %>
|
<%= render "shopping_cart" %>
|
||||||
<%= render "checkout_signin" %>
|
<%= render "checkout_signin" %>
|
||||||
<%= render "checkout_payment" %>
|
<%= render "checkout_payment" %>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
<div class="share-link border-bottom">
|
<div class="share-link border-bottom">
|
||||||
<h3>Share a Link:</h3>
|
<h3>Share a Link:</h3>
|
||||||
<div class="link-contents">
|
<div class="link-contents" id="link-contents">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right"><a id="btn-share-copy" class="button-orange">COPY LINK</a></div>
|
<div class="right"><a id="btn-share-copy" class="button-orange">COPY LINK</a></div>
|
||||||
|
|
|
||||||
|
|
@ -452,6 +452,17 @@ if defined?(Bundler)
|
||||||
# This would transparently migrate your existing Marshal-serialized cookies into the new JSON-based format.
|
# This would transparently migrate your existing Marshal-serialized cookies into the new JSON-based format.
|
||||||
config.action_dispatch.cookies_serializer = :hybrid
|
config.action_dispatch.cookies_serializer = :hybrid
|
||||||
|
|
||||||
config.jam_class_card_wait_period_year = 1
|
config.jam_class_card_wait_period_year = 1
|
||||||
|
|
||||||
|
config.paypal_mode = 'sandbox' #sandbox or live
|
||||||
|
config.app_id = 'APP-80W284485P519543T' # this is constant across all sandbox accts
|
||||||
|
config.paypal_username = 'seth+ppmerchant_api1.jamkazam.com' # seth+ppmerchant@jamkazam.com' # 'seth+ppmerchant_api1.jamkazam.com'
|
||||||
|
config.paypal_client_id = 'AZ5CCd8lHNntZ0ddxk_Wvo6LUaACd-bsMr7OPu_M1oI9vPN3d89mdSKswns9GEKB57qIwTT9_NyyK1c8'
|
||||||
|
config.paypal_client_secret = 'EIK0KWr8G5ntzYyJzbGCrsdr62aaJw2PdtXJrQddI9WuraBxJfiL4aMtKWu7Cyo4ACt13hLvCqg84HjP'
|
||||||
|
config.paypal_password = 'UXDKMPLYNE5YRLMK'
|
||||||
|
config.paypal_signature = 'AFcWxV21C7fd0v3bYYYRCpSSRl31AmvhBjN40M8etif4EA.L8EyMxdsu'
|
||||||
|
config.paypal_debug = true
|
||||||
|
config.paypal_express_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout' # drop sandbox for production
|
||||||
|
config.paypal_admin_only = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
PayPal::SDK.configure(
|
||||||
|
:mode => Rails.configuration.paypal_mode,
|
||||||
|
:username => Rails.configuration.paypal_username,
|
||||||
|
:password => Rails.configuration.paypal_password,
|
||||||
|
:signature => Rails.configuration.paypal_signature,
|
||||||
|
:ssl_options => { } )
|
||||||
|
|
||||||
|
# :client_id => Rails.configuration.paypal_client_id,
|
||||||
|
# :client_secret => Rails.configuration.paypal_client_secret,
|
||||||
|
|
||||||
|
|
||||||
|
PayPal::SDK.logger = Logging.logger['PayPal']
|
||||||
|
PayPal::SDK.logger.level = Rails.configuration.paypal_debug ? Logger::DEBUG : Logger::INFO
|
||||||
|
|
@ -77,6 +77,7 @@ Rails.application.routes.draw do
|
||||||
get '/auth/:provider/callback', :to => 'sessions#oauth_callback'
|
get '/auth/:provider/callback', :to => 'sessions#oauth_callback'
|
||||||
get '/auth/failure', :to => 'sessions#failure'
|
get '/auth/failure', :to => 'sessions#failure'
|
||||||
get '/auth/has_google_auth', :to => 'sessions#has_google_auth'
|
get '/auth/has_google_auth', :to => 'sessions#has_google_auth'
|
||||||
|
get '/auth/paypal/checkout', :to => 'sessions#paypal_express_checkout'
|
||||||
|
|
||||||
# session info page
|
# session info page
|
||||||
get '/sessions/:id/details' => 'music_sessions#session_info', :as => 'music_scheduled_session_detail'
|
get '/sessions/:id/details' => 'music_sessions#session_info', :as => 'music_scheduled_session_detail'
|
||||||
|
|
@ -125,6 +126,8 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
get '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse'
|
get '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse'
|
||||||
|
|
||||||
|
get '/paypal/checkout/start' => 'api_pay_pal#start_checkout'
|
||||||
|
|
||||||
# embed resque-web if this is development mode
|
# embed resque-web if this is development mode
|
||||||
if Rails.env == "development" || Rails.application.config.allow_spikes
|
if Rails.env == "development" || Rails.application.config.allow_spikes
|
||||||
require 'resque/server'
|
require 'resque/server'
|
||||||
|
|
@ -260,7 +263,6 @@ Rails.application.routes.draw do
|
||||||
match '/music_notations/:id' => 'api_music_notations#download', :via => :get, :as => :download_music_notation
|
match '/music_notations/:id' => 'api_music_notations#download', :via => :get, :as => :download_music_notation
|
||||||
match '/music_notations/:id' => 'api_music_notations#delete', :via => :delete, :as => :delete_music_notation
|
match '/music_notations/:id' => 'api_music_notations#delete', :via => :delete, :as => :delete_music_notation
|
||||||
|
|
||||||
|
|
||||||
# Backing track_show
|
# Backing track_show
|
||||||
match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list'
|
match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list'
|
||||||
|
|
||||||
|
|
@ -363,6 +365,10 @@ Rails.application.routes.draw do
|
||||||
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
|
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
|
||||||
match '/ios/order_placed' => 'api_jam_tracks#ios_order_placed', :via => :post
|
match '/ios/order_placed' => 'api_jam_tracks#ios_order_placed', :via => :post
|
||||||
|
|
||||||
|
# paypal
|
||||||
|
match '/paypal/checkout/detail' => 'api_pay_pal#checkout_detail', :via => :post
|
||||||
|
match '/paypal/checkout/confirm' => 'api_pay_pal#confirm_purchase', :via => :post
|
||||||
|
|
||||||
# sale info
|
# sale info
|
||||||
match '/payment_histories' => 'api_payment_histories#index', :via => :get
|
match '/payment_histories' => 'api_payment_histories#index', :via => :get
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,753 @@
|
||||||
|
/*!
|
||||||
|
* clipboard.js v1.5.15
|
||||||
|
* https://zenorocha.github.io/clipboard.js
|
||||||
|
*
|
||||||
|
* Licensed MIT © Zeno Rocha
|
||||||
|
*/
|
||||||
|
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||||
|
/**
|
||||||
|
* A polyfill for Element.matches()
|
||||||
|
*/
|
||||||
|
if (Element && !Element.prototype.matches) {
|
||||||
|
var proto = Element.prototype;
|
||||||
|
|
||||||
|
proto.matches = proto.matchesSelector ||
|
||||||
|
proto.mozMatchesSelector ||
|
||||||
|
proto.msMatchesSelector ||
|
||||||
|
proto.oMatchesSelector ||
|
||||||
|
proto.webkitMatchesSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest parent that matches a selector.
|
||||||
|
*
|
||||||
|
* @param {Element} element
|
||||||
|
* @param {String} selector
|
||||||
|
* @return {Function}
|
||||||
|
*/
|
||||||
|
function closest (element, selector) {
|
||||||
|
while (element && element !== document) {
|
||||||
|
if (element.matches(selector)) return element;
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = closest;
|
||||||
|
|
||||||
|
},{}],2:[function(require,module,exports){
|
||||||
|
var closest = require('./closest');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates event to a selector.
|
||||||
|
*
|
||||||
|
* @param {Element} element
|
||||||
|
* @param {String} selector
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} callback
|
||||||
|
* @param {Boolean} useCapture
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function delegate(element, selector, type, callback, useCapture) {
|
||||||
|
var listenerFn = listener.apply(this, arguments);
|
||||||
|
|
||||||
|
element.addEventListener(type, listenerFn, useCapture);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy: function() {
|
||||||
|
element.removeEventListener(type, listenerFn, useCapture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds closest match and invokes callback.
|
||||||
|
*
|
||||||
|
* @param {Element} element
|
||||||
|
* @param {String} selector
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {Function}
|
||||||
|
*/
|
||||||
|
function listener(element, selector, type, callback) {
|
||||||
|
return function(e) {
|
||||||
|
e.delegateTarget = closest(e.target, selector);
|
||||||
|
|
||||||
|
if (e.delegateTarget) {
|
||||||
|
callback.call(element, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = delegate;
|
||||||
|
|
||||||
|
},{"./closest":1}],3:[function(require,module,exports){
|
||||||
|
/**
|
||||||
|
* Check if argument is a HTML element.
|
||||||
|
*
|
||||||
|
* @param {Object} value
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
exports.node = function(value) {
|
||||||
|
return value !== undefined
|
||||||
|
&& value instanceof HTMLElement
|
||||||
|
&& value.nodeType === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if argument is a list of HTML elements.
|
||||||
|
*
|
||||||
|
* @param {Object} value
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
exports.nodeList = function(value) {
|
||||||
|
var type = Object.prototype.toString.call(value);
|
||||||
|
|
||||||
|
return value !== undefined
|
||||||
|
&& (type === '[object NodeList]' || type === '[object HTMLCollection]')
|
||||||
|
&& ('length' in value)
|
||||||
|
&& (value.length === 0 || exports.node(value[0]));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if argument is a string.
|
||||||
|
*
|
||||||
|
* @param {Object} value
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
exports.string = function(value) {
|
||||||
|
return typeof value === 'string'
|
||||||
|
|| value instanceof String;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if argument is a function.
|
||||||
|
*
|
||||||
|
* @param {Object} value
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
exports.fn = function(value) {
|
||||||
|
var type = Object.prototype.toString.call(value);
|
||||||
|
|
||||||
|
return type === '[object Function]';
|
||||||
|
};
|
||||||
|
|
||||||
|
},{}],4:[function(require,module,exports){
|
||||||
|
var is = require('./is');
|
||||||
|
var delegate = require('delegate');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates all params and calls the right
|
||||||
|
* listener function based on its target type.
|
||||||
|
*
|
||||||
|
* @param {String|HTMLElement|HTMLCollection|NodeList} target
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function listen(target, type, callback) {
|
||||||
|
if (!target && !type && !callback) {
|
||||||
|
throw new Error('Missing required arguments');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is.string(type)) {
|
||||||
|
throw new TypeError('Second argument must be a String');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is.fn(callback)) {
|
||||||
|
throw new TypeError('Third argument must be a Function');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.node(target)) {
|
||||||
|
return listenNode(target, type, callback);
|
||||||
|
}
|
||||||
|
else if (is.nodeList(target)) {
|
||||||
|
return listenNodeList(target, type, callback);
|
||||||
|
}
|
||||||
|
else if (is.string(target)) {
|
||||||
|
return listenSelector(target, type, callback);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an event listener to a HTML element
|
||||||
|
* and returns a remove listener function.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} node
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function listenNode(node, type, callback) {
|
||||||
|
node.addEventListener(type, callback);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy: function() {
|
||||||
|
node.removeEventListener(type, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event listener to a list of HTML elements
|
||||||
|
* and returns a remove listener function.
|
||||||
|
*
|
||||||
|
* @param {NodeList|HTMLCollection} nodeList
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function listenNodeList(nodeList, type, callback) {
|
||||||
|
Array.prototype.forEach.call(nodeList, function(node) {
|
||||||
|
node.addEventListener(type, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy: function() {
|
||||||
|
Array.prototype.forEach.call(nodeList, function(node) {
|
||||||
|
node.removeEventListener(type, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event listener to a selector
|
||||||
|
* and returns a remove listener function.
|
||||||
|
*
|
||||||
|
* @param {String} selector
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function listenSelector(selector, type, callback) {
|
||||||
|
return delegate(document.body, selector, type, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = listen;
|
||||||
|
|
||||||
|
},{"./is":3,"delegate":2}],5:[function(require,module,exports){
|
||||||
|
function select(element) {
|
||||||
|
var selectedText;
|
||||||
|
|
||||||
|
if (element.nodeName === 'SELECT') {
|
||||||
|
element.focus();
|
||||||
|
|
||||||
|
selectedText = element.value;
|
||||||
|
}
|
||||||
|
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
||||||
|
element.focus();
|
||||||
|
element.setSelectionRange(0, element.value.length);
|
||||||
|
|
||||||
|
selectedText = element.value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (element.hasAttribute('contenteditable')) {
|
||||||
|
element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
var selection = window.getSelection();
|
||||||
|
var range = document.createRange();
|
||||||
|
|
||||||
|
range.selectNodeContents(element);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
|
||||||
|
selectedText = selection.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = select;
|
||||||
|
|
||||||
|
},{}],6:[function(require,module,exports){
|
||||||
|
function E () {
|
||||||
|
// Keep this empty so it's easier to inherit from
|
||||||
|
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
|
||||||
|
}
|
||||||
|
|
||||||
|
E.prototype = {
|
||||||
|
on: function (name, callback, ctx) {
|
||||||
|
var e = this.e || (this.e = {});
|
||||||
|
|
||||||
|
(e[name] || (e[name] = [])).push({
|
||||||
|
fn: callback,
|
||||||
|
ctx: ctx
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
once: function (name, callback, ctx) {
|
||||||
|
var self = this;
|
||||||
|
function listener () {
|
||||||
|
self.off(name, listener);
|
||||||
|
callback.apply(ctx, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
listener._ = callback
|
||||||
|
return this.on(name, listener, ctx);
|
||||||
|
},
|
||||||
|
|
||||||
|
emit: function (name) {
|
||||||
|
var data = [].slice.call(arguments, 1);
|
||||||
|
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
||||||
|
var i = 0;
|
||||||
|
var len = evtArr.length;
|
||||||
|
|
||||||
|
for (i; i < len; i++) {
|
||||||
|
evtArr[i].fn.apply(evtArr[i].ctx, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
off: function (name, callback) {
|
||||||
|
var e = this.e || (this.e = {});
|
||||||
|
var evts = e[name];
|
||||||
|
var liveEvents = [];
|
||||||
|
|
||||||
|
if (evts && callback) {
|
||||||
|
for (var i = 0, len = evts.length; i < len; i++) {
|
||||||
|
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
|
||||||
|
liveEvents.push(evts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove event from queue to prevent memory leak
|
||||||
|
// Suggested by https://github.com/lazd
|
||||||
|
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
|
||||||
|
|
||||||
|
(liveEvents.length)
|
||||||
|
? e[name] = liveEvents
|
||||||
|
: delete e[name];
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = E;
|
||||||
|
|
||||||
|
},{}],7:[function(require,module,exports){
|
||||||
|
(function (global, factory) {
|
||||||
|
if (typeof define === "function" && define.amd) {
|
||||||
|
define(['module', 'select'], factory);
|
||||||
|
} else if (typeof exports !== "undefined") {
|
||||||
|
factory(module, require('select'));
|
||||||
|
} else {
|
||||||
|
var mod = {
|
||||||
|
exports: {}
|
||||||
|
};
|
||||||
|
factory(mod, global.select);
|
||||||
|
global.clipboardAction = mod.exports;
|
||||||
|
}
|
||||||
|
})(this, function (module, _select) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _select2 = _interopRequireDefault(_select);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : {
|
||||||
|
default: obj
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||||||
|
return typeof obj;
|
||||||
|
} : function (obj) {
|
||||||
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) {
|
||||||
|
if (!(instance instanceof Constructor)) {
|
||||||
|
throw new TypeError("Cannot call a class as a function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _createClass = function () {
|
||||||
|
function defineProperties(target, props) {
|
||||||
|
for (var i = 0; i < props.length; i++) {
|
||||||
|
var descriptor = props[i];
|
||||||
|
descriptor.enumerable = descriptor.enumerable || false;
|
||||||
|
descriptor.configurable = true;
|
||||||
|
if ("value" in descriptor) descriptor.writable = true;
|
||||||
|
Object.defineProperty(target, descriptor.key, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (Constructor, protoProps, staticProps) {
|
||||||
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||||
|
if (staticProps) defineProperties(Constructor, staticProps);
|
||||||
|
return Constructor;
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
var ClipboardAction = function () {
|
||||||
|
/**
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
function ClipboardAction(options) {
|
||||||
|
_classCallCheck(this, ClipboardAction);
|
||||||
|
|
||||||
|
this.resolveOptions(options);
|
||||||
|
this.initSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines base properties passed from constructor.
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
_createClass(ClipboardAction, [{
|
||||||
|
key: 'resolveOptions',
|
||||||
|
value: function resolveOptions() {
|
||||||
|
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||||
|
|
||||||
|
this.action = options.action;
|
||||||
|
this.emitter = options.emitter;
|
||||||
|
this.target = options.target;
|
||||||
|
this.text = options.text;
|
||||||
|
this.trigger = options.trigger;
|
||||||
|
|
||||||
|
this.selectedText = '';
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'initSelection',
|
||||||
|
value: function initSelection() {
|
||||||
|
if (this.text) {
|
||||||
|
this.selectFake();
|
||||||
|
} else if (this.target) {
|
||||||
|
this.selectTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'selectFake',
|
||||||
|
value: function selectFake() {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
|
||||||
|
|
||||||
|
this.removeFake();
|
||||||
|
|
||||||
|
this.fakeHandlerCallback = function () {
|
||||||
|
return _this.removeFake();
|
||||||
|
};
|
||||||
|
this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || true;
|
||||||
|
|
||||||
|
this.fakeElem = document.createElement('textarea');
|
||||||
|
// Prevent zooming on iOS
|
||||||
|
this.fakeElem.style.fontSize = '12pt';
|
||||||
|
// Reset box model
|
||||||
|
this.fakeElem.style.border = '0';
|
||||||
|
this.fakeElem.style.padding = '0';
|
||||||
|
this.fakeElem.style.margin = '0';
|
||||||
|
// Move element out of screen horizontally
|
||||||
|
this.fakeElem.style.position = 'absolute';
|
||||||
|
this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
|
||||||
|
// Move element to the same position vertically
|
||||||
|
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
this.fakeElem.addEventListener('focus', window.scrollTo(0, yPosition));
|
||||||
|
this.fakeElem.style.top = yPosition + 'px';
|
||||||
|
|
||||||
|
this.fakeElem.setAttribute('readonly', '');
|
||||||
|
this.fakeElem.value = this.text;
|
||||||
|
|
||||||
|
document.body.appendChild(this.fakeElem);
|
||||||
|
|
||||||
|
this.selectedText = (0, _select2.default)(this.fakeElem);
|
||||||
|
this.copyText();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'removeFake',
|
||||||
|
value: function removeFake() {
|
||||||
|
if (this.fakeHandler) {
|
||||||
|
document.body.removeEventListener('click', this.fakeHandlerCallback);
|
||||||
|
this.fakeHandler = null;
|
||||||
|
this.fakeHandlerCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fakeElem) {
|
||||||
|
document.body.removeChild(this.fakeElem);
|
||||||
|
this.fakeElem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'selectTarget',
|
||||||
|
value: function selectTarget() {
|
||||||
|
this.selectedText = (0, _select2.default)(this.target);
|
||||||
|
this.copyText();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'copyText',
|
||||||
|
value: function copyText() {
|
||||||
|
var succeeded = void 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
succeeded = document.execCommand(this.action);
|
||||||
|
} catch (err) {
|
||||||
|
succeeded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleResult(succeeded);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'handleResult',
|
||||||
|
value: function handleResult(succeeded) {
|
||||||
|
this.emitter.emit(succeeded ? 'success' : 'error', {
|
||||||
|
action: this.action,
|
||||||
|
text: this.selectedText,
|
||||||
|
trigger: this.trigger,
|
||||||
|
clearSelection: this.clearSelection.bind(this)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'clearSelection',
|
||||||
|
value: function clearSelection() {
|
||||||
|
if (this.target) {
|
||||||
|
this.target.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'destroy',
|
||||||
|
value: function destroy() {
|
||||||
|
this.removeFake();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'action',
|
||||||
|
set: function set() {
|
||||||
|
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
|
||||||
|
|
||||||
|
this._action = action;
|
||||||
|
|
||||||
|
if (this._action !== 'copy' && this._action !== 'cut') {
|
||||||
|
throw new Error('Invalid "action" value, use either "copy" or "cut"');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get: function get() {
|
||||||
|
return this._action;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'target',
|
||||||
|
set: function set(target) {
|
||||||
|
if (target !== undefined) {
|
||||||
|
if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
|
||||||
|
if (this.action === 'copy' && target.hasAttribute('disabled')) {
|
||||||
|
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
|
||||||
|
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._target = target;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid "target" value, use a valid Element');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get: function get() {
|
||||||
|
return this._target;
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return ClipboardAction;
|
||||||
|
}();
|
||||||
|
|
||||||
|
module.exports = ClipboardAction;
|
||||||
|
});
|
||||||
|
|
||||||
|
},{"select":5}],8:[function(require,module,exports){
|
||||||
|
(function (global, factory) {
|
||||||
|
if (typeof define === "function" && define.amd) {
|
||||||
|
define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory);
|
||||||
|
} else if (typeof exports !== "undefined") {
|
||||||
|
factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener'));
|
||||||
|
} else {
|
||||||
|
var mod = {
|
||||||
|
exports: {}
|
||||||
|
};
|
||||||
|
factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener);
|
||||||
|
global.clipboard = mod.exports;
|
||||||
|
}
|
||||||
|
})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
|
||||||
|
|
||||||
|
var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
|
||||||
|
|
||||||
|
var _goodListener2 = _interopRequireDefault(_goodListener);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : {
|
||||||
|
default: obj
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) {
|
||||||
|
if (!(instance instanceof Constructor)) {
|
||||||
|
throw new TypeError("Cannot call a class as a function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _createClass = function () {
|
||||||
|
function defineProperties(target, props) {
|
||||||
|
for (var i = 0; i < props.length; i++) {
|
||||||
|
var descriptor = props[i];
|
||||||
|
descriptor.enumerable = descriptor.enumerable || false;
|
||||||
|
descriptor.configurable = true;
|
||||||
|
if ("value" in descriptor) descriptor.writable = true;
|
||||||
|
Object.defineProperty(target, descriptor.key, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (Constructor, protoProps, staticProps) {
|
||||||
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||||
|
if (staticProps) defineProperties(Constructor, staticProps);
|
||||||
|
return Constructor;
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
function _possibleConstructorReturn(self, call) {
|
||||||
|
if (!self) {
|
||||||
|
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||||
|
}
|
||||||
|
|
||||||
|
return call && (typeof call === "object" || typeof call === "function") ? call : self;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _inherits(subClass, superClass) {
|
||||||
|
if (typeof superClass !== "function" && superClass !== null) {
|
||||||
|
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
||||||
|
constructor: {
|
||||||
|
value: subClass,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
configurable: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Clipboard = function (_Emitter) {
|
||||||
|
_inherits(Clipboard, _Emitter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
function Clipboard(trigger, options) {
|
||||||
|
_classCallCheck(this, Clipboard);
|
||||||
|
|
||||||
|
var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));
|
||||||
|
|
||||||
|
_this.resolveOptions(options);
|
||||||
|
_this.listenClick(trigger);
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines if attributes would be resolved using internal setter functions
|
||||||
|
* or custom functions that were passed in the constructor.
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
_createClass(Clipboard, [{
|
||||||
|
key: 'resolveOptions',
|
||||||
|
value: function resolveOptions() {
|
||||||
|
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||||
|
|
||||||
|
this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
|
||||||
|
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
|
||||||
|
this.text = typeof options.text === 'function' ? options.text : this.defaultText;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'listenClick',
|
||||||
|
value: function listenClick(trigger) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
|
||||||
|
return _this2.onClick(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'onClick',
|
||||||
|
value: function onClick(e) {
|
||||||
|
var trigger = e.delegateTarget || e.currentTarget;
|
||||||
|
|
||||||
|
if (this.clipboardAction) {
|
||||||
|
this.clipboardAction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clipboardAction = new _clipboardAction2.default({
|
||||||
|
action: this.action(trigger),
|
||||||
|
target: this.target(trigger),
|
||||||
|
text: this.text(trigger),
|
||||||
|
trigger: trigger,
|
||||||
|
emitter: this
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'defaultAction',
|
||||||
|
value: function defaultAction(trigger) {
|
||||||
|
return getAttributeValue('action', trigger);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'defaultTarget',
|
||||||
|
value: function defaultTarget(trigger) {
|
||||||
|
var selector = getAttributeValue('target', trigger);
|
||||||
|
|
||||||
|
if (selector) {
|
||||||
|
return document.querySelector(selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'defaultText',
|
||||||
|
value: function defaultText(trigger) {
|
||||||
|
return getAttributeValue('text', trigger);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'destroy',
|
||||||
|
value: function destroy() {
|
||||||
|
this.listener.destroy();
|
||||||
|
|
||||||
|
if (this.clipboardAction) {
|
||||||
|
this.clipboardAction.destroy();
|
||||||
|
this.clipboardAction = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return Clipboard;
|
||||||
|
}(_tinyEmitter2.default);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to retrieve attribute value.
|
||||||
|
* @param {String} suffix
|
||||||
|
* @param {Element} element
|
||||||
|
*/
|
||||||
|
function getAttributeValue(suffix, element) {
|
||||||
|
var attribute = 'data-clipboard-' + suffix;
|
||||||
|
|
||||||
|
if (!element.hasAttribute(attribute)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return element.getAttribute(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Clipboard;
|
||||||
|
});
|
||||||
|
|
||||||
|
},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)
|
||||||
|
});
|
||||||
|
|
@ -1,611 +0,0 @@
|
||||||
/*
|
|
||||||
* jQuery Clipboard :: Fork of zClip :: Uses ZeroClipboard v1.2.3
|
|
||||||
*
|
|
||||||
* https://github.com/valeriansaliou/jquery.clipboard
|
|
||||||
* http://www.steamdev.com/zclip/
|
|
||||||
*
|
|
||||||
* Copyright 2013, Valérian Saliou
|
|
||||||
* Copyright 2011, SteamDev
|
|
||||||
*
|
|
||||||
* Released under the MIT license.
|
|
||||||
* http://www.opensource.org/licenses/mit-license.php
|
|
||||||
*
|
|
||||||
* Version: v1.2
|
|
||||||
* Date: Sun Dec 1, 2013
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Component: jQuery Clipboard */
|
|
||||||
(function ($) {
|
|
||||||
var $clip = null;
|
|
||||||
var $is_loaded = false;
|
|
||||||
|
|
||||||
$.fn.clipboard = function (params) {
|
|
||||||
if ((typeof params == 'object' && !params.length) || (typeof params == 'undefined')) {
|
|
||||||
var settings = $.extend({
|
|
||||||
path: 'jquery.clipboard.swf',
|
|
||||||
copy: null,
|
|
||||||
beforeCopy: null,
|
|
||||||
afterCopy: null,
|
|
||||||
clickAfter: true
|
|
||||||
}, (params || {}));
|
|
||||||
|
|
||||||
return this.each(function () {
|
|
||||||
var o = $(this);
|
|
||||||
|
|
||||||
if (o.is(':visible') && (typeof settings.copy == 'string' || $.isFunction(settings.copy))) {
|
|
||||||
if ($.isFunction(settings.copy)) {
|
|
||||||
o.bind('Clipboard_copy',settings.copy);
|
|
||||||
}
|
|
||||||
if ($.isFunction(settings.beforeCopy)) {
|
|
||||||
o.bind('Clipboard_beforeCopy',settings.beforeCopy);
|
|
||||||
}
|
|
||||||
if ($.isFunction(settings.afterCopy)) {
|
|
||||||
o.bind('Clipboard_afterCopy',settings.afterCopy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($clip === null) {
|
|
||||||
$clip = new ZeroClipboard(null, {
|
|
||||||
moviePath: settings.path,
|
|
||||||
trustedDomains: '*',
|
|
||||||
hoverClass: 'hover',
|
|
||||||
activeClass: 'active'
|
|
||||||
});
|
|
||||||
|
|
||||||
$clip.on('load', function(client) {
|
|
||||||
client.on('mouseover', function (client) {
|
|
||||||
$(this).trigger('mouseenter');
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('mouseout', function (client) {
|
|
||||||
$(this).trigger('mouseleave');
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('mousedown', function (client) {
|
|
||||||
$(this).trigger('mousedown');
|
|
||||||
|
|
||||||
if (!$.isFunction(settings.copy)) {
|
|
||||||
client.setText(settings.copy);
|
|
||||||
} else {
|
|
||||||
client.setText($(this).triggerHandler('Clipboard_copy'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($.isFunction(settings.beforeCopy)) {
|
|
||||||
$(this).trigger('Clipboard_beforeCopy');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('complete', function (client, args) {
|
|
||||||
if ($.isFunction(settings.afterCopy)) {
|
|
||||||
$(this).trigger('Clipboard_afterCopy');
|
|
||||||
} else {
|
|
||||||
$(this).removeClass('hover');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.clickAfter) {
|
|
||||||
$(this).trigger('click');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$clip.glue(o[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
||||||
|
|
||||||
/* Component: ZeroClipboard */
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
|
||||||
var _camelizeCssPropName = function() {
|
|
||||||
var matcherRegex = /\-([a-z])/g, replacerFn = function(match, group) {
|
|
||||||
return group.toUpperCase();
|
|
||||||
};
|
|
||||||
return function(prop) {
|
|
||||||
return prop.replace(matcherRegex, replacerFn);
|
|
||||||
};
|
|
||||||
}();
|
|
||||||
var _getStyle = function(el, prop) {
|
|
||||||
var value, camelProp, tagName, possiblePointers, i, len;
|
|
||||||
if (window.getComputedStyle) {
|
|
||||||
value = window.getComputedStyle(el, null).getPropertyValue(prop);
|
|
||||||
} else {
|
|
||||||
camelProp = _camelizeCssPropName(prop);
|
|
||||||
if (el.currentStyle) {
|
|
||||||
value = el.currentStyle[camelProp];
|
|
||||||
} else {
|
|
||||||
value = el.style[camelProp];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (prop === "cursor") {
|
|
||||||
if (!value || value === "auto") {
|
|
||||||
tagName = el.tagName.toLowerCase();
|
|
||||||
possiblePointers = [ "a" ];
|
|
||||||
for (i = 0, len = possiblePointers.length; i < len; i++) {
|
|
||||||
if (tagName === possiblePointers[i]) {
|
|
||||||
return "pointer";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
var _elementMouseOver = function(event) {
|
|
||||||
if (!ZeroClipboard.prototype._singleton) return;
|
|
||||||
if (!event) {
|
|
||||||
event = window.event;
|
|
||||||
}
|
|
||||||
var target;
|
|
||||||
if (this !== window) {
|
|
||||||
target = this;
|
|
||||||
} else if (event.target) {
|
|
||||||
target = event.target;
|
|
||||||
} else if (event.srcElement) {
|
|
||||||
target = event.srcElement;
|
|
||||||
}
|
|
||||||
ZeroClipboard.prototype._singleton.setCurrent(target);
|
|
||||||
};
|
|
||||||
var _addEventHandler = function(element, method, func) {
|
|
||||||
if (element.addEventListener) {
|
|
||||||
element.addEventListener(method, func, false);
|
|
||||||
} else if (element.attachEvent) {
|
|
||||||
element.attachEvent("on" + method, func);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var _removeEventHandler = function(element, method, func) {
|
|
||||||
if (element.removeEventListener) {
|
|
||||||
element.removeEventListener(method, func, false);
|
|
||||||
} else if (element.detachEvent) {
|
|
||||||
element.detachEvent("on" + method, func);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var _addClass = function(element, value) {
|
|
||||||
if (element.addClass) {
|
|
||||||
element.addClass(value);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
if (value && typeof value === "string") {
|
|
||||||
var classNames = (value || "").split(/\s+/);
|
|
||||||
if (element.nodeType === 1) {
|
|
||||||
if (!element.className) {
|
|
||||||
element.className = value;
|
|
||||||
} else {
|
|
||||||
var className = " " + element.className + " ", setClass = element.className;
|
|
||||||
for (var c = 0, cl = classNames.length; c < cl; c++) {
|
|
||||||
if (className.indexOf(" " + classNames[c] + " ") < 0) {
|
|
||||||
setClass += " " + classNames[c];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element.className = setClass.replace(/^\s+|\s+$/g, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
var _removeClass = function(element, value) {
|
|
||||||
if (element.removeClass) {
|
|
||||||
element.removeClass(value);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
if (value && typeof value === "string" || value === undefined) {
|
|
||||||
var classNames = (value || "").split(/\s+/);
|
|
||||||
if (element.nodeType === 1 && element.className) {
|
|
||||||
if (value) {
|
|
||||||
var className = (" " + element.className + " ").replace(/[\n\t]/g, " ");
|
|
||||||
for (var c = 0, cl = classNames.length; c < cl; c++) {
|
|
||||||
className = className.replace(" " + classNames[c] + " ", " ");
|
|
||||||
}
|
|
||||||
element.className = className.replace(/^\s+|\s+$/g, "");
|
|
||||||
} else {
|
|
||||||
element.className = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
var _getZoomFactor = function() {
|
|
||||||
var rect, physicalWidth, logicalWidth, zoomFactor = 1;
|
|
||||||
if (typeof document.body.getBoundingClientRect === "function") {
|
|
||||||
rect = document.body.getBoundingClientRect();
|
|
||||||
physicalWidth = rect.right - rect.left;
|
|
||||||
logicalWidth = document.body.offsetWidth;
|
|
||||||
zoomFactor = Math.round(physicalWidth / logicalWidth * 100) / 100;
|
|
||||||
}
|
|
||||||
return zoomFactor;
|
|
||||||
};
|
|
||||||
var _getDOMObjectPosition = function(obj) {
|
|
||||||
var info = {
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
zIndex: 999999999
|
|
||||||
};
|
|
||||||
var zi = _getStyle(obj, "z-index");
|
|
||||||
if (zi && zi !== "auto") {
|
|
||||||
info.zIndex = parseInt(zi, 10);
|
|
||||||
}
|
|
||||||
if (obj.getBoundingClientRect) {
|
|
||||||
var rect = obj.getBoundingClientRect();
|
|
||||||
var pageXOffset, pageYOffset, zoomFactor;
|
|
||||||
if ("pageXOffset" in window && "pageYOffset" in window) {
|
|
||||||
pageXOffset = window.pageXOffset;
|
|
||||||
pageYOffset = window.pageYOffset;
|
|
||||||
} else {
|
|
||||||
zoomFactor = _getZoomFactor();
|
|
||||||
pageXOffset = Math.round(document.documentElement.scrollLeft / zoomFactor);
|
|
||||||
pageYOffset = Math.round(document.documentElement.scrollTop / zoomFactor);
|
|
||||||
}
|
|
||||||
var leftBorderWidth = document.documentElement.clientLeft || 0;
|
|
||||||
var topBorderWidth = document.documentElement.clientTop || 0;
|
|
||||||
info.left = rect.left + pageXOffset - leftBorderWidth;
|
|
||||||
info.top = rect.top + pageYOffset - topBorderWidth;
|
|
||||||
info.width = "width" in rect ? rect.width : rect.right - rect.left;
|
|
||||||
info.height = "height" in rect ? rect.height : rect.bottom - rect.top;
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
};
|
|
||||||
var _noCache = function(path, options) {
|
|
||||||
var useNoCache = !(options && options.useNoCache === false);
|
|
||||||
if (useNoCache) {
|
|
||||||
return (path.indexOf("?") === -1 ? "?" : "&") + "nocache=" + new Date().getTime();
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var _vars = function(options) {
|
|
||||||
var str = [];
|
|
||||||
var origins = [];
|
|
||||||
if (options.trustedOrigins) {
|
|
||||||
if (typeof options.trustedOrigins === "string") {
|
|
||||||
origins.push(options.trustedOrigins);
|
|
||||||
} else if (typeof options.trustedOrigins === "object" && "length" in options.trustedOrigins) {
|
|
||||||
origins = origins.concat(options.trustedOrigins);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.trustedDomains) {
|
|
||||||
if (typeof options.trustedDomains === "string") {
|
|
||||||
origins.push(options.trustedDomains);
|
|
||||||
} else if (typeof options.trustedDomains === "object" && "length" in options.trustedDomains) {
|
|
||||||
origins = origins.concat(options.trustedDomains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (origins.length) {
|
|
||||||
str.push("trustedOrigins=" + encodeURIComponent(origins.join(",")));
|
|
||||||
}
|
|
||||||
if (typeof options.amdModuleId === "string" && options.amdModuleId) {
|
|
||||||
str.push("amdModuleId=" + encodeURIComponent(options.amdModuleId));
|
|
||||||
}
|
|
||||||
if (typeof options.cjsModuleId === "string" && options.cjsModuleId) {
|
|
||||||
str.push("cjsModuleId=" + encodeURIComponent(options.cjsModuleId));
|
|
||||||
}
|
|
||||||
return str.join("&");
|
|
||||||
};
|
|
||||||
var _inArray = function(elem, array) {
|
|
||||||
if (array.indexOf) {
|
|
||||||
return array.indexOf(elem);
|
|
||||||
}
|
|
||||||
for (var i = 0, length = array.length; i < length; i++) {
|
|
||||||
if (array[i] === elem) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
var _prepGlue = function(elements) {
|
|
||||||
if (typeof elements === "string") throw new TypeError("ZeroClipboard doesn't accept query strings.");
|
|
||||||
if (!elements.length) return [ elements ];
|
|
||||||
return elements;
|
|
||||||
};
|
|
||||||
var _dispatchCallback = function(func, element, instance, args, async) {
|
|
||||||
if (async) {
|
|
||||||
window.setTimeout(function() {
|
|
||||||
func.call(element, instance, args);
|
|
||||||
}, 0);
|
|
||||||
} else {
|
|
||||||
func.call(element, instance, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var currentElement, gluedElements = [], flashState = {};
|
|
||||||
var ZeroClipboard = function(elements, options) {
|
|
||||||
if (elements) (ZeroClipboard.prototype._singleton || this).glue(elements);
|
|
||||||
if (ZeroClipboard.prototype._singleton) return ZeroClipboard.prototype._singleton;
|
|
||||||
ZeroClipboard.prototype._singleton = this;
|
|
||||||
this.options = {};
|
|
||||||
for (var kd in _defaults) this.options[kd] = _defaults[kd];
|
|
||||||
for (var ko in options) this.options[ko] = options[ko];
|
|
||||||
this.handlers = {};
|
|
||||||
if (!flashState.hasOwnProperty(this.options.moviePath)) {
|
|
||||||
flashState[this.options.moviePath] = {
|
|
||||||
noflash: !ZeroClipboard.detectFlashSupport(),
|
|
||||||
wrongflash: false,
|
|
||||||
ready: false,
|
|
||||||
version: "0.0.0"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (flashState[this.options.moviePath].noflash === false) {
|
|
||||||
_bridge();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.setCurrent = function(element) {
|
|
||||||
currentElement = element;
|
|
||||||
this.reposition();
|
|
||||||
var titleAttr = element.getAttribute("title");
|
|
||||||
if (titleAttr) {
|
|
||||||
this.setTitle(titleAttr);
|
|
||||||
}
|
|
||||||
var useHandCursor = this.options.forceHandCursor === true || _getStyle(element, "cursor") === "pointer";
|
|
||||||
_setHandCursor.call(this, useHandCursor);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.setText = function(newText) {
|
|
||||||
if (newText && newText !== "") {
|
|
||||||
this.options.text = newText;
|
|
||||||
if (this.ready()) this.flashBridge.setText(newText);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.setTitle = function(newTitle) {
|
|
||||||
if (newTitle && newTitle !== "") this.htmlBridge.setAttribute("title", newTitle);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.setSize = function(width, height) {
|
|
||||||
if (this.ready()) this.flashBridge.setSize(width, height);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.setHandCursor = function(enabled) {
|
|
||||||
enabled = typeof enabled === "boolean" ? enabled : !!enabled;
|
|
||||||
_setHandCursor.call(this, enabled);
|
|
||||||
this.options.forceHandCursor = enabled;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
var _setHandCursor = function(enabled) {
|
|
||||||
if (this.ready()) this.flashBridge.setHandCursor(enabled);
|
|
||||||
};
|
|
||||||
ZeroClipboard.version = "1.2.3";
|
|
||||||
var _defaults = {
|
|
||||||
moviePath: "ZeroClipboard.swf",
|
|
||||||
trustedOrigins: null,
|
|
||||||
text: null,
|
|
||||||
hoverClass: "zeroclipboard-is-hover",
|
|
||||||
activeClass: "zeroclipboard-is-active",
|
|
||||||
allowScriptAccess: "sameDomain",
|
|
||||||
useNoCache: true,
|
|
||||||
forceHandCursor: false
|
|
||||||
};
|
|
||||||
ZeroClipboard.setDefaults = function(options) {
|
|
||||||
for (var ko in options) _defaults[ko] = options[ko];
|
|
||||||
};
|
|
||||||
ZeroClipboard.destroy = function() {
|
|
||||||
if (ZeroClipboard.prototype._singleton) {
|
|
||||||
ZeroClipboard.prototype._singleton.unglue(gluedElements);
|
|
||||||
var bridge = ZeroClipboard.prototype._singleton.htmlBridge;
|
|
||||||
if (bridge && bridge.parentNode) {
|
|
||||||
bridge.parentNode.removeChild(bridge);
|
|
||||||
}
|
|
||||||
delete ZeroClipboard.prototype._singleton;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ZeroClipboard.detectFlashSupport = function() {
|
|
||||||
var hasFlash = false;
|
|
||||||
if (typeof ActiveXObject === "function") {
|
|
||||||
try {
|
|
||||||
if (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) {
|
|
||||||
hasFlash = true;
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
if (!hasFlash && navigator.mimeTypes["application/x-shockwave-flash"]) {
|
|
||||||
hasFlash = true;
|
|
||||||
}
|
|
||||||
return hasFlash;
|
|
||||||
};
|
|
||||||
var _amdModuleId = null;
|
|
||||||
var _cjsModuleId = null;
|
|
||||||
var _bridge = function() {
|
|
||||||
var flashBridge, len;
|
|
||||||
var client = ZeroClipboard.prototype._singleton;
|
|
||||||
var container = document.getElementById("global-zeroclipboard-html-bridge");
|
|
||||||
if (!container) {
|
|
||||||
var opts = {};
|
|
||||||
for (var ko in client.options) opts[ko] = client.options[ko];
|
|
||||||
opts.amdModuleId = _amdModuleId;
|
|
||||||
opts.cjsModuleId = _cjsModuleId;
|
|
||||||
var flashvars = _vars(opts);
|
|
||||||
var html = ' <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="global-zeroclipboard-flash-bridge" width="100%" height="100%"> <param name="movie" value="' + client.options.moviePath + _noCache(client.options.moviePath, client.options) + '"/> <param name="allowScriptAccess" value="' + client.options.allowScriptAccess + '"/> <param name="scale" value="exactfit"/> <param name="loop" value="false"/> <param name="menu" value="false"/> <param name="quality" value="best" /> <param name="bgcolor" value="#ffffff"/> <param name="wmode" value="transparent"/> <param name="flashvars" value="' + flashvars + '"/> <embed src="' + client.options.moviePath + _noCache(client.options.moviePath, client.options) + '" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="100%" height="100%" name="global-zeroclipboard-flash-bridge" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="' + flashvars + '" scale="exactfit"> </embed> </object>';
|
|
||||||
container = document.createElement("div");
|
|
||||||
container.id = "global-zeroclipboard-html-bridge";
|
|
||||||
container.setAttribute("class", "global-zeroclipboard-container");
|
|
||||||
container.style.position = "absolute";
|
|
||||||
container.style.left = "0px";
|
|
||||||
container.style.top = "-9999px";
|
|
||||||
container.style.width = "15px";
|
|
||||||
container.style.height = "15px";
|
|
||||||
container.style.zIndex = "9999";
|
|
||||||
document.body.appendChild(container);
|
|
||||||
container.innerHTML = html;
|
|
||||||
}
|
|
||||||
client.htmlBridge = container;
|
|
||||||
flashBridge = document["global-zeroclipboard-flash-bridge"];
|
|
||||||
if (flashBridge && (len = flashBridge.length)) {
|
|
||||||
flashBridge = flashBridge[len - 1];
|
|
||||||
}
|
|
||||||
client.flashBridge = flashBridge || container.children[0].lastElementChild;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.resetBridge = function() {
|
|
||||||
if (this.htmlBridge) {
|
|
||||||
this.htmlBridge.style.left = "0px";
|
|
||||||
this.htmlBridge.style.top = "-9999px";
|
|
||||||
this.htmlBridge.removeAttribute("title");
|
|
||||||
}
|
|
||||||
if (currentElement) {
|
|
||||||
_removeClass(currentElement, this.options.activeClass);
|
|
||||||
currentElement = null;
|
|
||||||
}
|
|
||||||
this.options.text = null;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.ready = function() {
|
|
||||||
return flashState[this.options.moviePath].ready === true;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.reposition = function() {
|
|
||||||
if (!currentElement) return false;
|
|
||||||
var pos = _getDOMObjectPosition(currentElement);
|
|
||||||
this.htmlBridge.style.top = pos.top + "px";
|
|
||||||
this.htmlBridge.style.left = pos.left + "px";
|
|
||||||
this.htmlBridge.style.width = pos.width + "px";
|
|
||||||
this.htmlBridge.style.height = pos.height + "px";
|
|
||||||
this.htmlBridge.style.zIndex = pos.zIndex + 1;
|
|
||||||
this.setSize(pos.width, pos.height);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.dispatch = function(eventName, args) {
|
|
||||||
ZeroClipboard.prototype._singleton.receiveEvent(eventName, args);
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.on = function(eventName, func) {
|
|
||||||
var events = eventName.toString().split(/\s/g), added = {};
|
|
||||||
for (var i = 0, len = events.length; i < len; i++) {
|
|
||||||
eventName = events[i].toLowerCase().replace(/^on/, "");
|
|
||||||
added[eventName] = true;
|
|
||||||
if (!this.handlers[eventName]) {
|
|
||||||
this.handlers[eventName] = func;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (added.noflash && flashState[this.options.moviePath].noflash) {
|
|
||||||
this.receiveEvent("onNoFlash", {});
|
|
||||||
}
|
|
||||||
if (added.wrongflash && flashState[this.options.moviePath].wrongflash) {
|
|
||||||
this.receiveEvent("onWrongFlash", {
|
|
||||||
flashVersion: flashState[this.options.moviePath].version
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (added.load && flashState[this.options.moviePath].ready) {
|
|
||||||
this.receiveEvent("onLoad", {
|
|
||||||
flashVersion: flashState[this.options.moviePath].version
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.addEventListener = ZeroClipboard.prototype.on;
|
|
||||||
ZeroClipboard.prototype.off = function(eventName, func) {
|
|
||||||
var events = eventName.toString().split(/\s/g);
|
|
||||||
for (var i = 0; i < events.length; i++) {
|
|
||||||
eventName = events[i].toLowerCase().replace(/^on/, "");
|
|
||||||
for (var event in this.handlers) {
|
|
||||||
if (event === eventName && this.handlers[event] === func) {
|
|
||||||
delete this.handlers[event];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.removeEventListener = ZeroClipboard.prototype.off;
|
|
||||||
ZeroClipboard.prototype.receiveEvent = function(eventName, args) {
|
|
||||||
eventName = eventName.toString().toLowerCase().replace(/^on/, "");
|
|
||||||
var element = currentElement;
|
|
||||||
var performCallbackAsync = true;
|
|
||||||
switch (eventName) {
|
|
||||||
case "load":
|
|
||||||
if (args && args.flashVersion) {
|
|
||||||
if (!_isFlashVersionSupported(args.flashVersion)) {
|
|
||||||
this.receiveEvent("onWrongFlash", {
|
|
||||||
flashVersion: args.flashVersion
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
flashState[this.options.moviePath].ready = true;
|
|
||||||
flashState[this.options.moviePath].version = args.flashVersion;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "wrongflash":
|
|
||||||
if (args && args.flashVersion && !_isFlashVersionSupported(args.flashVersion)) {
|
|
||||||
flashState[this.options.moviePath].wrongflash = true;
|
|
||||||
flashState[this.options.moviePath].version = args.flashVersion;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "mouseover":
|
|
||||||
_addClass(element, this.options.hoverClass);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "mouseout":
|
|
||||||
_removeClass(element, this.options.hoverClass);
|
|
||||||
this.resetBridge();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "mousedown":
|
|
||||||
_addClass(element, this.options.activeClass);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "mouseup":
|
|
||||||
_removeClass(element, this.options.activeClass);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "datarequested":
|
|
||||||
var targetId = element.getAttribute("data-clipboard-target"), targetEl = !targetId ? null : document.getElementById(targetId);
|
|
||||||
if (targetEl) {
|
|
||||||
var textContent = targetEl.value || targetEl.textContent || targetEl.innerText;
|
|
||||||
if (textContent) {
|
|
||||||
this.setText(textContent);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var defaultText = element.getAttribute("data-clipboard-text");
|
|
||||||
if (defaultText) {
|
|
||||||
this.setText(defaultText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
performCallbackAsync = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "complete":
|
|
||||||
this.options.text = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (this.handlers[eventName]) {
|
|
||||||
var func = this.handlers[eventName];
|
|
||||||
if (typeof func === "string" && typeof window[func] === "function") {
|
|
||||||
func = window[func];
|
|
||||||
}
|
|
||||||
if (typeof func === "function") {
|
|
||||||
_dispatchCallback(func, element, this, args, performCallbackAsync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.glue = function(elements) {
|
|
||||||
elements = _prepGlue(elements);
|
|
||||||
for (var i = 0; i < elements.length; i++) {
|
|
||||||
if (elements[i] && elements[i].nodeType === 1) {
|
|
||||||
if (_inArray(elements[i], gluedElements) == -1) {
|
|
||||||
gluedElements.push(elements[i]);
|
|
||||||
_addEventHandler(elements[i], "mouseover", _elementMouseOver);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
ZeroClipboard.prototype.unglue = function(elements) {
|
|
||||||
elements = _prepGlue(elements);
|
|
||||||
for (var i = 0; i < elements.length; i++) {
|
|
||||||
_removeEventHandler(elements[i], "mouseover", _elementMouseOver);
|
|
||||||
var arrayIndex = _inArray(elements[i], gluedElements);
|
|
||||||
if (arrayIndex != -1) gluedElements.splice(arrayIndex, 1);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
function _isFlashVersionSupported(flashVersion) {
|
|
||||||
return parseFloat(flashVersion.replace(/,/g, ".").replace(/[^0-9\.]/g, "")) >= 10;
|
|
||||||
}
|
|
||||||
if (typeof define === "function" && define.amd) {
|
|
||||||
define([ "require", "exports", "module" ], function(require, exports, module) {
|
|
||||||
_amdModuleId = module && module.id || null;
|
|
||||||
return ZeroClipboard;
|
|
||||||
});
|
|
||||||
} else if (typeof module === "object" && module && typeof module.exports === "object" && module.exports) {
|
|
||||||
_cjsModuleId = module.id || null;
|
|
||||||
module.exports = ZeroClipboard;
|
|
||||||
} else {
|
|
||||||
window.ZeroClipboard = ZeroClipboard;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
Loading…
Reference in New Issue