Initial go live commit

This commit is contained in:
Fergal Moran
2014-05-28 22:39:34 +01:00
parent 82361f32b0
commit c81aa0e775
53 changed files with 1676 additions and 649 deletions

View File

@@ -12,23 +12,26 @@ PIPELINE_JS = {
},
'backbone': {
'source_filenames': (
'js/lib/jquery.js',
'js/lib/underscore.js',
'js/lib/underscore.templatehelpers.js',
'js/lib/backbone.js',
'js/lib/backbone.syphon.js',
'js/lib/backbone.associations.js',
'js/lib/backbone.marionette.js',
)
},
'lib': {
'source_filenames': (
'js/lib/ace/uncompressed/jquery.js',
'js/lib/ace/uncompressed/jquery-ui.js',
'js/lib/backbone.associations.js',
'js/lib/backbone.syphon.js',
'js/lib/ace/uncompressed/bootstrap.js',
'js/lib/moment.js',
'js/lib/ajaxfileupload.js',
'js/lib/ace/uncompressed/ace.js',
'js/lib/ace/uncompressed/ace-elements.js',
'js/lib/ace/uncompressed/select2.js',
'js/lib/ace/uncompressed/fuelux/fuelux.wizard.js',
'js/lib/ace/ace/elements.wizard.js',
'js/lib/typeahead.js',
'js/lib/ace/uncompressed/bootstrap-wysiwyg.js',
@@ -40,6 +43,7 @@ PIPELINE_JS = {
'js/lib/sm/soundmanager2.js',
'js/lib/jasny.fileinput.js',
'js/lib/jquery.fileupload.js',
'js/lib/jquery.fileupload-process.js',
'js/lib/jquery.fileupload-audio.js',

View File

@@ -11,7 +11,7 @@ from django.http import HttpResponse, HttpResponseNotFound
from annoying.decorators import render_to
from django.shortcuts import render_to_response
from django.utils import simplejson
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.views.decorators.http import require_POST
from jfu.http import upload_receive, UploadResponse
@@ -234,6 +234,7 @@ def upload_avatar_image(request):
@require_POST
@login_required
@csrf_protect
def upload(request):
# The assumption here is that jQuery File Upload
# has been configured to send files one at a time.
@@ -242,16 +243,22 @@ def upload(request):
try:
uid = request.POST['upload-hash']
in_file = request.FILES['file'] if request.FILES else None
fileName, extension = os.path.splitext(in_file.name)
file_name, extension = os.path.splitext(in_file.name)
file_storage = FileSystemStorage(location=os.path.join(settings.CACHE_ROOT, "mixes"))
cache_file = file_storage.save("%s%s" % (uid, extension), ContentFile(in_file.read()))
response = ''
create_waveform_task.delay(in_file=os.path.join(file_storage.base_location, cache_file), uid=uid)
try:
create_waveform_task.delay(in_file=os.path.join(file_storage.base_location, cache_file), uid=uid)
except:
logger.debug("Unable to connect to celery")
response = 'Unable to connect to waveform generation task, there may be a delay in getting your mix online'
file_dict = {
'size': in_file.size,
'uid': uid
'response': response,
'size': in_file.size,
'uid': uid
}
return UploadResponse(request, file_dict)

View File

@@ -53,14 +53,15 @@ class MixResource(BackboneCompatibleResource):
(self._meta.resource_name, trailing_slash()),
self.wrap_view('get_search'),
name="api_get_search"),
url(r"^(?P<resource_name>%s)/(?P<id>[\d]+)/$" %
self._meta.resource_name, self.wrap_view('dispatch_detail'),
url(r"^(?P<resource_name>%s)/(?P<id>[\d]+)%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('dispatch_detail'),
name="api_dispatch_detail"),
url(r"^(?P<resource_name>%s)/random/$" %
self._meta.resource_name, self.wrap_view('dispatch_random'),
name="api_dispatch_random"),
url(r"^(?P<resource_name>%s)/(?P<slug>[\w\d-]+)/$" %
self._meta.resource_name,
url(r"^(?P<resource_name>%s)/(?P<slug>[\w\d-]+)%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
url(r"^(?P<resource_name>%s)/(?P<slug>\w[\w/-]*)/comments%s$" %
(self._meta.resource_name, trailing_slash()),

View File

@@ -78,13 +78,12 @@ class UserResource(BackboneCompatibleResource):
ret = super(UserResource, self).obj_update(bundle, skip_errors, **kwargs)
"""
bundle.obj.update_follower(bundle.request.user,
bundle.data['favourited'])
"""
try:
update_geo_info_task.delay(ip_address=bundle.request.META['REMOTE_ADDR'],
profile_id=bundle.request.user.get_profile().id)
except:
pass
update_geo_info_task.delay(ip_address=bundle.request.META['REMOTE_ADDR'],
profile_id=bundle.request.user.get_profile().id)
return ret
def dehydrate_description(self, bundle):
@@ -122,6 +121,7 @@ class UserResource(BackboneCompatibleResource):
bundle.data['date_joined'] = bundle.obj.user.date_joined
bundle.data['last_login'] = bundle.obj.user.last_login
bundle.data['mix_count'] = bundle.obj.mix_count
bundle.data['thumbnail'] = bundle.obj.get_small_profile_image()
return bundle

View File

@@ -139,10 +139,6 @@ class UserProfile(_BaseModel):
except IOError, ex:
self.logger.warn("Error getting medium profile image: %s", ex.message)
def get_small_profile_image_2(self):
ret = self.get_small_profile_image()
return ret
def get_small_profile_image(self):
try:
if self.avatar_type == 'custom':

View File

@@ -46,6 +46,8 @@ def get_javascript(request, template_name):
'CURRENT_USER_ID': request.user.get_profile().id if not request.user.is_anonymous() else -1,
'CURRENT_USER_NAME': request.user.get_profile().get_nice_name() if not request.user.is_anonymous() else -1,
'CURRENT_USER_URL': request.user.get_profile().get_profile_url() if not request.user.is_anonymous() else -1,
'CURRENT_USER_SLUG': request.user.get_profile().slug if not request.user.is_anonymous() else -1,
'CURRENT_USER_CANHOMEPAGE': request.user.has_perm('spa.mix_add_homepage') or request.user.user.is_staff() if not request.user.is_anonymous() else False,
'AVATAR_IMAGE': request.user.get_profile().get_small_profile_image() if not request.user.is_anonymous() else ""
})
return render_to_response(

View File

@@ -47,7 +47,6 @@
error: =>
utils.showWarning "Ooops", "Error playing mix. If you have a flash blocker, please disable it for this site. Otherwise, do please try again."
return
com.podnoms.storage.setItem "now_playing", @id
return
isPlayingId: (id) ->

View File

@@ -65,7 +65,6 @@
utils.showWarning("Ooops", "Error playing mix. If you have a flash blocker, please disable it for this site. Otherwise, do please try again.");
}
});
com.podnoms.storage.setItem("now_playing", _this.id);
});
};

View File

@@ -164,4 +164,3 @@ define ['app.lib/dssView', 'utils', 'ace-editable', 'typeahead'],
deferred.promise()
EditableView

View File

@@ -6,7 +6,7 @@
define(['app.lib/dssView', 'utils', 'ace-editable', 'typeahead'], function(DssView, utils) {
var EditableView;
EditableView = (function(_super) {
return EditableView = (function(_super) {
__extends(EditableView, _super);
@@ -201,7 +201,6 @@
return EditableView;
})(DssView);
return EditableView;
});
}).call(this);

View File

@@ -15,6 +15,7 @@
App.addInitializer ->
@headerRegion.show(new App.HeaderApp.Views.Header())
@sidebarRegion.show(new App.SidebarApp.Views.SidebarView())
App.addInitializer ->
$(document).on("click", "a[href]:not([data-bypass])", (evt) ->
@@ -60,7 +61,7 @@
@listenTo @vent, "user:follow", (model)->
console.log "App(vent): user:follow"
user = new UserItem({id: com.podnoms.settings.currentUser })
user = new App.UserApp.Models.UserItem({id: com.podnoms.settings.currentUser })
target = com.podnoms.settings.urlRoot + "user/" + model.get("id") + "/"
user.fetch(
success: =>

View File

@@ -0,0 +1,8 @@
@Dss.module "ActivityApp.Models", (Models, App, Backbone) ->
class Models.ActivityItem extends Backbone.Model
urlRoot: com.podnoms.settings.urlRoot + "activity/"
parse: (model) ->
model.human_date = moment(model.date).fromNow()
model

View File

@@ -0,0 +1,19 @@
@Dss.module "ActivityApp.Models", (Models, App, Backbone) ->
class Models.ActivityCollection extends Backbone.Collection
model: Models.ActivityItem
url:com.podnoms.settings.urlRoot + "activity/"
initialize: ->
@listenTo App.vent, "model:activity:new", (url) =>
console.log("ActivityCollection: activity:new")
item = new Models.ActivityItem()
item.fetch
url: url,
success: (response) =>
console.log("ActivityCollection: item fetched")
console.log(response)
@add response
comparator: (item)->
-item.id

View File

@@ -0,0 +1,8 @@
@Dss.module "ActivityApp.Views", (Views, App, Backbone, Marionette, $) ->
class Views.ActivityItemView extends Marionette.ItemView
template: "activityitemview"
tagName: "div"
onRender: (itemView) ->
$(itemView.el).addClass('animated flash')
true

View File

@@ -0,0 +1,21 @@
@Dss.module "ActivityApp.Views", (Views, App, Backbone, Marionette) ->
class Views.ActivityListView extends Marionette.CompositeView
template: "activitylistview"
tagName: "div"
className: "widget-box"
itemView: Views.ActivityItemView
itemViewContainer: "#activity-container"
initialize: ->
@collection = new App.ActivityApp.Models.ActivityCollection
@collection.fetch()
#kinda primordial (but working) support for sorted collection view
#based on https://github.com/marionettejs/backbone.marionette/wiki/Adding-support-for-sorted-collections
appendHtml: (collectionView, itemView, index) ->
childrenContainer = (if collectionView.itemViewContainer then collectionView.$(collectionView.itemViewContainer) else collectionView.$el)
children = childrenContainer.children()
if children.size() <= index
childrenContainer.append itemView.el
else
childrenContainer.children().eq(index).before itemView.el

View File

@@ -25,7 +25,7 @@
onShow: ->
@searchRegion.show(new App.SearchApp.Views.SearchView())
if com.podnoms.settings.currentUser != -1
@notificationsRegion.show(new NotificationsListView())
@notificationsRegion.show(new App.NotificationApp.Views.NotificationsListView())
showRandom: ->
console.log("headerView: showRandom")

View File

@@ -0,0 +1,185 @@
@Dss.module "MixApp.Views", (Views, App, Backbone, Marionette, $) ->
class Views.MixEditView extends App.Lib.EditableView
@func = null
template: "mixeditview"
events:
"click #login": "login"
ui:
image: "#mix-image"
progress: "#mix-upload-progress"
uploadError: '#mix-upload-error'
initialize: ->
@guid = utils.generateGuid()
@uploadState = -1
@detailsEntered = false
@patch = false
Dropzone.autoDiscover = false
onDomRefresh: ->
$('#mix-upload-wizard', @el).ace_wizard().on("change", (e, info) =>
if info.step is 1 and @uploadState is 0
console.log "MixEditView: No mix uploaded"
@ui.uploadError.text("Please add a mix")
@ui.uploadError.fadeIn()
false
else if @uploadState > 0
true
else
$('#step1').addClass("alert-danger")
false
).on("finished", (e) =>
console.log("Finished")
@saveChanges()
)
onRender: ->
console.log("MixEditView: onRender")
$('.progress', @el).hide()
@sendImage = false
if not @model.id
$('input[name="upload-hash"]', @el).val(@guid)
else
$('#header-step1', @el).remove()
$('#step1', @el).remove()
$('#header-step2', @el).addClass("active")
$('#step2', @el).addClass("active")
$('.progress', @el).hide()
@patch = true
@uploadState = 2
$("#mix-upload-form", @el).dropzone
addRemoveLinks: true
dictDefaultMessage: "<span class=\"bigger-150 bolder\"><i class=\"icon-caret-right red\"></i> Drop files</span> to upload \t\t\t\t<span class=\"smaller-80 grey\">(or click)</span> <br /> \t\t\t\t<i class=\"fa fa-cloud-upload fa-5x blue\"></i>"
maxFilesize: 512
sending: (file, xhr, formData) =>
$('.progress', @el).show()
xhr.setRequestHeader "X-CSRFToken", utils.getCookie("csrftoken")
@uploadState = 1
uploadprogress: (e, progress, bytesSent) =>
$('.progress', @el).show()
percentage = Math.round(progress)
@ui.progress.css("width", percentage + "%").parent().attr "data-percent", percentage + "%"
complete: (file)=>
if file.status != "error"
@uploadState = 2
@checkRedirect()
$("#genres", @el).select2
placeholder: "Start typing and choose from list or create your own."
minimumInputLength: 1
multiple: true
ajax: # instead of writing the function to execute the request we use Select2's convenient helper
url: "/ajax/lookup/genre/"
dataType: "json"
data: (term, page) ->
q: term
results: (data, page) -> # parse the results into the format expected by Select2.
# since we are using custom formatting functions we do not need to alter remote JSON data
results: data
formatResult: (genre) ->
genre.description
formatSelection: (genre) ->
"<div class='select2-user-result'>" + genre.description + "</div>"
initSelection: (element, callback) =>
console.log("MixEditView: genres:initSelection")
result = []
genres = @model.get("genres")
unless genres is `undefined`
genres.each (data) ->
result.push
id: data.get("id")
description: data.get("description")
callback result
"""
createSearchChoice: (term, data) ->
if $(data).filter(->
@description.localeCompare(term) is 0
).length is 0
id: term
text: term
"""
$('#mix-image-fileinput', @el).on "change", (evt) =>
file = evt.target.files[0]
if file
@sendImage = true
true
true
saveChanges: =>
console.log("MixEditView: saveChanges")
@model.set Backbone.Syphon.serialize($("#mix-details-form", @el)[0])
flair = Backbone.Syphon.serialize $("#mix-flair-form", @el)[0],
exclude: ["...", ""]
@model.set flair
@model.set "upload-hash", @guid
@model.set "upload-extension", $("#upload-extension", @el).val()
@model.set("genres", new App.GenresApp.Models.GenreCollection())
$.each $("#genres", @el).select2("data"), (i, item) =>
###
if @model.get("genres") is undefined
@model.set("genres", new GenreCollection())
###
@model.get("genres").add({id: item.id, description: item.text});
@model.unset "mix_image" unless @sendImage
@model.unset "comments"
@_saveChanges
patch: @patch
success: =>
if @sendImage
$.ajaxFileUpload
url: "/ajax/upload_mix_image/" + @model.get("id") + "/"
secureuri: false
fileElementId: "input[name='...']"
success: (data, status) =>
unless typeof (data.error) is "undefined"
unless data.error is ""
alert data.error
else
alert data.msg
else
$("#mix-upload-wizard", @el).hide()
@detailsEntered = true
@checkRedirect()
error: (data, status, e) ->
utils.showError e
else
$("#mix-upload-wizard", @el).hide()
@detailsEntered = true
@checkRedirect()
true
error: (model, response) ->
utils.showError "Error", "Something went wrong<br />Nerd stuff is: " + response
false
checkRedirect: ->
if @detailsEntered and @uploadState is 2
Backbone.history.navigate "/mix/" + @model.get("slug"),
trigger: true
login: ->
App.vent.trigger('app:login')

View File

@@ -47,14 +47,12 @@
#the el has not been added to the DOM at this stage, so we'll check for the bounds
#and if zero, rely on the composite view to call into this in it's onDomRefresh
"""
if App.audioController.isPlayingId @model.id
console.log "Re-wrapping player"
App.audioController.setupPlayerEl $(@el)
@ui.playButton.toggleClass("play", false).toggleClass("pause", false).toggleClass("resume", false)
@mixState = App.audioController.getMixState()
@_setupStateUI()
"""
#$(@el).on("resize", App.audioController.setupPlayerEl($(@el)));
return
@@ -101,7 +99,6 @@
mixFavourite: ->
console.log("MixItemView: favouriteMix")
app = require('app')
App.vent.trigger("mix:favourite", @model)
true

View File

@@ -0,0 +1,9 @@
@Dss.module "NotificationApp.Models", (Models, App, Backbone, Marionette, $, _, vent) ->
class Models.NotificationItem extends Backbone.Model
urlRoot: com.podnoms.settings.urlRoot + "activity/"
parse: (model) ->
model.human_date = moment(model.date).fromNow()
model
Models.NotificationItem

View File

@@ -0,0 +1,27 @@
@Dss.module "NotificationApp.Models", (Models, App, Backbone, Marionette, $, _, vent) ->
class Models.NotificationCollection extends Backbone.Collection
page: 0
model: Models.NotificationItem
limit: 5
newCount: ->
return @is_new
url: ->
com.podnoms.settings.urlRoot + "notification/?limit=" + @limit + "&offset=" + Math.max(@page - 1, 0) * @limit
initialize: ->
@listenTo App.vent, "model:notification:new", (url) =>
console.log("NotificationCollection: notification:new")
item = new NotificationItem()
item.fetch
url: url
success: (response) =>
console.log("NotificationCollection: item fetched")
console.log(response)
@add response
comparator: (item)->
-item.id
Models.NotificationCollection

View File

@@ -0,0 +1,4 @@
@Dss.module "NotificationApp.Views", (Views, App, Backbone, Marionette) ->
class Views.NotificationsItemView extends Marionette.CompositeView
template: "notificationsitemview"
tagName: "li"

View File

@@ -0,0 +1,51 @@
@Dss.module "NotificationApp.Views", (Views, App, Backbone, Marionette) ->
class Views.NotificationsListView extends Marionette.CompositeView
template: "notificationslistview"
itemView: Views.NotificationsItemView
itemViewContainer: "#notif_list_node"
tagName: "li"
events:
"click #notifications-dropdown": "showNotifications"
ui:
notificationSurround: "#notification-surround"
notificationCountBadge: "#notification-count-badge"
notificationCount: "#notification-count"
initialize: =>
#quick and dirty check to see if user is logged in
@collection = new App.NotificationApp.Models.NotificationCollection
@collection.fetch(
success: =>
@renderBeacon()
@collection.on 'add': (model, collection)=>
@collection.meta.is_new += 1
@renderBeacon(model)
error: =>
$(@ui.notificationSurround).hide()
)
renderBeacon: (model) ->
newCount = @collection.meta.is_new
if newCount == 0
$(@ui.notificationCount).text("Notifications")
$(@ui.notificationCountBadge).hide()
else
$(@ui.notificationCount).text(newCount + " new notifications")
$(@ui.notificationCountBadge).show()
$(@ui.notificationCountBadge).addClass('animate pulse')
$('#notification-icon', @el).addClass('icon-animated-bell')
$(@ui.notificationCountBadge).text(newCount)
showNotifications: ->
console.log("NotificationsListView: showNotifications")
$.ajax
url: '/ajax/mark_read/'
type: 'post'
success: =>
$(@ui.notificationCountBadge).hide()
@collection.meta.is_new = 0
Views.NotificationsListView

View File

@@ -0,0 +1,47 @@
@Dss.module "NowPlayingApp.Views", (Views, App, Backbone, Marionette, $) ->
class Views.NowPlayingView extends Marionette.ItemView
className: "now-playing"
events: {
"click #now-playing-play": "doPlay",
"click #now-playing-pause": "doPause"
}
initialize: (options)->
console.log "NowPlayingView: initialize " + options.source
@source = options.source
if options.source is 'mix'
@template = "nowplaying"
else
@template = _.template(LiveTemplate)
@listenTo(App.vent, 'mix:play', @mixPlay)
@listenTo(App.vent, 'mix:pause', @mixPause)
$('#now-playing-pause', @el).hide()
true
onRender: ->
@mixPlay()
true
mixPause: (model) ->
console.log "NowPlayingView: mixPause"
$('#now-playing-play', @el).show()
$('#now-playing-pause', @el).hide()
true
mixPlay: (model) ->
console.log "NowPlayingView: mixPlay"
$('#now-playing-play', @el).hide()
$('#now-playing-pause', @el).show()
true
doPlay: ->
console.log "NowPlayingView: doPlay"
App.vent.trigger('mix:play', @model)
true
doPause: ->
console.log "NowPlayingView: doPause"
App.vent.trigger('mix:pause', @model)
true

View File

@@ -0,0 +1,4 @@
@Dss.module "SearchApp.Views", (Views, App, Backbone, Marionette, $, _, vent) ->
class Views.SearchItemView extends Marionette.ItemView
template: "searchresultview"
el: "li"

View File

@@ -1,13 +1,17 @@
@Dss.module "SearchApp.Views", (Views, App, Backbone, Marionette, $, _, vent) ->
class Views.SearchView extends Marionette.Layout
class Views.SearchView extends Marionette.CompositeView
template: "search"
itemView: Views.SearchItemView
itemViewEl: "#search-results"
ui:
searchText: '#search-text'
events:
'keyup #search-text': 'doSearch'
'blur #search-text': 'closeSearch'
engine:
compile: (template) ->
compiled = _.template(template)
@@ -15,26 +19,33 @@
compiled context
closeSearch: () ->
$("#suggestions").fadeOut()
$(@itemViewEl).fadeOut()
appendHtml: ->
console.log("Appending html")
doSearch: () ->
inputString = @ui.searchText.val()
if inputString.length is 0
$("#suggestions").fadeOut()
$(@itemViewEl).fadeOut()
else
results = new MixCollection()
results = new App.MixApp.Models.MixCollection()
results.fetch
data: $.param(
limit: "4"
title__icontains: inputString
)
success: (data) ->
$("#suggestions", @el).find("li:gt(0)").remove()
$("#suggestions").fadeIn() # Hide the suggestions box
results.each (item)->
html = new SearchItemView()
$("#suggestions", @el).append html.template(item.attributes)
success: (data) =>
$(@itemViewEl).find("li:gt(0)").remove()
$(@itemViewEl).fadeIn()
results.each (item)=>
view = new Views.SearchItemView({model: item})
data = view.serializeData();
data = view.mixinTemplateHelpers(data);
template = view.getTemplate();
html = Marionette.Renderer.render(template, data);
$(@itemViewEl).append html
return
Views.SearchView

View File

@@ -0,0 +1,52 @@
@Dss.module "SidebarApp.Views", (Views, App, Backbone, Marionette, $, _, vent) ->
class Views.SidebarView extends Marionette.Layout
template: "sidebarview"
regions:
topRegion: '#sidebar-now-playing'
streamRegion: '#sidebar-stream-content'
initialize: ->
this.listenTo(App.vent, 'mix:init', @mixInit)
this.listenTo(App.vent, 'mix:play', @mixPlay)
this.listenTo(App.vent, 'mix:pause', @mixPause)
this.listenTo(App.vent, 'live:started', @liveStarted)
this.listenTo(App.vent, 'live:stopped', @liveStopped)
return
onRender: ->
return
onShow: ->
@streamRegion.show(new App.ActivityApp.Views.ActivityListView())
$(@topRegion.el).hide()
return
mixInit: (model) ->
$(@topRegion.el).show()
@topRegion.show(new App.NowPlayingApp.Views.NowPlayingView({
source: 'mix',
model: model
}))
liveStarted: ->
$(@topRegion.el).show()
$.getJSON "/ajax/live_now_playing/", (data) =>
App.vent.trigger('mix:stop')
@topRegion.show(new NowPlayingView({
source: 'live'
model: new Backbone.Model({
mix_image: "/static/img/radio.jpg",
item_url: "",
title: data.description,
user_profile_url: "",
user_name: data.title
})
}))
true
liveStopped: ->
@topRegion.close()
Views.SidebarView

View File

@@ -4,8 +4,8 @@
className: "row"
events:
"click #follow-button": -> vent.trigger("user:follow", @model)
"click #follow-button-login": -> vent.trigger("app:login", @model)
"click #follow-button": -> App.vent.trigger("user:follow", @model)
"click #follow-button-login": -> App.vent.trigger("app:login", @model)
initialize: =>
@listenTo(@model, 'change:is_following', @render)

View File

@@ -3,8 +3,8 @@
template: "userprofileview"
events:
"click #follow-button": -> vent.trigger("user:follow", @model)
"click #follow-button-login": -> vent.trigger("app:login", @model)
"click #follow-button": -> App.vent.trigger("user:follow", @model)
"click #follow-button-login": -> App.vent.trigger("app:login", @model)
initialize: =>
@listenTo(@model, 'change:is_following', @render)

View File

@@ -14,3 +14,6 @@ _.addTemplateHelpers
secondsToHms: (d) ->
utils.secondsToHms d
canHomepage: () ->
return com.podnoms.settings.canHomepage

View File

@@ -40,14 +40,13 @@
@setupPlayer el, data.stream_url
peneloPlay.startPlaying
success: =>
vent.trigger("mix:play", model)
vent.trigger("live:stop")
App.vent.trigger("mix:play", model)
App.vent.trigger("live:stop")
utils.checkPlayCount()
return
error: =>
utils.showWarning "Ooops", "Error playing mix. If you have a flash blocker, please disable it for this site. Otherwise, do please try again."
return
com.podnoms.storage.setItem "now_playing", @id
return
isPlayingId: (id) ->

View File

@@ -54,7 +54,7 @@
download_allowed: true,
is_featured: false
});
App.contentRegion.show(new MixEditView({model: mix}))
App.contentRegion.show(new App.MixApp.Views.MixEditView({model: mix}))
true
editMix: (slug) ->
@@ -62,7 +62,7 @@
mix = new App.MixApp.Models.MixItem({id: slug})
mix.fetch(
success: ->
App.contentRegion.show(new MixEditView(model: mix))
App.contentRegion.show(new App.MixApp.Views.MixEditView(model: mix))
)
true

View File

@@ -8,168 +8,175 @@
((if h > 0 then h + ":" else "")) + ((if m > 0 then ((if h > 0 and m < 10 then "0" else "")) + m + ":" else "00:")) + ((if s < 10 then "0" else "")) + s
else
"00:00:00"
@i = {}
@bounds = {}
@_player = undefined
@_src = undefined
ui = {}
bounds = {}
_player = undefined
_src = undefined
_mouseDown: (event) ->
@_player.setPosition (Peneloplay._getCalculatedDuration() / 100) * ((event.pageX - @ui.wrapper.offset().left) / @bounds.waveformWidth) * 100 if @_player
_player.setPosition (@_getCalculatedDuration() / 100) * ((event.pageX - ui.wrapper.offset().left) / bounds.waveformWidth) * 100 if _player
$(event.currentTarget).mouseup $.proxy(@_mouseDown, this)
return
_mouseMove: (event) ->
@ui.seekHead.css "left", (event.offsetX + @bounds.waveformLeft) #.fadeIn('fast');
ui.seekHead.css "left", (event.offsetX + bounds.waveformLeft) #.fadeIn('fast');
return
_mouseLeave: ->
@ui.seekHead.hide()
@ui.wrapper.unbind "mousedown"
@ui.wrapper.unbind "mousemove"
ui.seekHead.hide()
ui.wrapper.unbind "mousedown"
ui.wrapper.unbind "mousemove"
return
_mouseEnter: ->
@ui.wrapper.mousedown $.proxy(@_mouseDown, this)
@ui.wrapper.mousemove $.proxy(@_mouseMove, this)
@ui.seekHead.show()
ui.wrapper.mousedown $.proxy(@_mouseDown, this)
ui.wrapper.mousemove $.proxy(@_mouseMove, this)
ui.seekHead.show()
return
_hookupMouseEntryEvents: ->
if @ui.wrapper
@ui.wrapper.mouseenter $.proxy(@_mouseEnter, this)
@ui.wrapper.mouseleave $.proxy(@_mouseLeave, this)
if ui.wrapper
ui.wrapper.mouseenter $.proxy(@_mouseEnter, this)
ui.wrapper.mouseleave $.proxy(@_mouseLeave, this)
return
_teardownMouseEntryEvents: ->
if @ui.wrapper
@ui.wrapper.unbind "mouseenter"
@ui.wrapper.unbind "mouseleave"
if ui.wrapper
ui.wrapper.unbind "mouseenter"
ui.wrapper.unbind "mouseleave"
return
_getCalculatedDuration: ->
if @_player.instanceOptions.isMovieStar
@_player.duration
if _player.instanceOptions.isMovieStar
_player.duration
else
@_player.durationEstimate
_player.durationEstimate
stopPlaying: ->
if @_player
@_player.stop()
soundManager.destroySound @_player.sID
if _player
_player.stop()
soundManager.destroySound _player.sID
@_teardownMouseEntryEvents()
@_player = `undefined`
_player = `undefined`
return
setupPlayer: (el, src) ->
@_src = src if src
_src = src if src
@_el = el
#check all required elements are available
@ui.instance = el.find(".pnp-instance")
@ui.wrapper = el.find(".pnp-wrapper")
@ui.waveform = el.find(".pnp-waveform")
@ui.timeDuration = el.find(".pnp-time-display-label-duration")
@ui.timeElapsed = el.find(".pnp-time-display-label-elapsed")
@ui.downloadOverlay = el.find(".pnp-download-overlay")
@ui.playedOverlay = el.find(".pnp-played-overlay")
@ui.seekHead = el.find(".pnp-seekhead")
@bounds.waveformWidth = @ui.waveform.width()
@bounds.waveformHeight = @ui.waveform.height()
@bounds.waveformLeft = @ui.waveform.position().left
Peneloplay.setupUIWidgets()
ui.instance = el.find(".pnp-instance")
ui.wrapper = el.find(".pnp-wrapper")
ui.waveform = el.find(".pnp-waveform")
ui.timeDuration = el.find(".pnp-time-display-label-duration")
ui.timeElapsed = el.find(".pnp-time-display-label-elapsed")
ui.downloadOverlay = el.find(".pnp-download-overlay")
ui.playedOverlay = el.find(".pnp-played-overlay")
ui.seekHead = el.find(".pnp-seekhead")
bounds.waveformWidth = ui.waveform.width()
bounds.waveformHeight = ui.waveform.height()
bounds.waveformLeft = ui.waveform.position().left
@setupUIWidgets()
this
setupUIWidgets: ->
@ui.seekHead.animate
top: @ui.waveform.position().top
left: @ui.waveform.position().left
height: @ui.waveform.height()
ui.seekHead.animate
top: ui.waveform.position().top
left: ui.waveform.position().left
height: ui.waveform.height()
@ui.timeElapsed.show()
@ui.timeElapsed.animate
top: @ui.waveform.position().top
left: @ui.waveform.position().left
ui.timeElapsed.show()
ui.timeElapsed.animate
top: ui.waveform.position().top
left: ui.waveform.position().left
@ui.timeDuration.show()
@ui.timeDuration.animate
top: @ui.waveform.position().top
left: (@ui.waveform.position().left + @ui.waveform.width()) - @ui.timeDuration.width()
ui.timeDuration.show()
ui.timeDuration.animate
top: ui.waveform.position().top
left: (ui.waveform.position().left + ui.waveform.width()) - ui.timeDuration.width()
@ui.downloadOverlay.animate
top: @ui.waveform.position().top
left: @ui.waveform.position().left
height: @ui.waveform.height()
if soundManager.html5.mp3
ui.downloadOverlay.hide()
else
ui.downloadOverlay.animate
top: ui.waveform.position().top
left: ui.waveform.position().left
height: ui.waveform.height()
@ui.playedOverlay.show()
@ui.playedOverlay.animate
top: @ui.waveform.position().top
left: @ui.waveform.position().left
height: @ui.waveform.height()
if @_player
if @_player.paused
percentageWidth = (bounds.waveformWidth / 100) * ((@_player.position / Peneloplay._getCalculatedDuration()) * 100)
@ui.playedOverlay.css "width", percentageWidth
Peneloplay._hookupMouseEntryEvents()
ui.playedOverlay.show()
ui.playedOverlay.animate
top: ui.waveform.position().top
left: ui.waveform.position().left
height: ui.waveform.height()
if _player
if _player.paused
percentageWidth = (bounds.waveformWidth / 100) * ((_player.position / @_getCalculatedDuration()) * 100)
ui.playedOverlay.css "width", percentageWidth
@_hookupMouseEntryEvents()
return
startPlaying: (args)->
console.log "Starting to play"
Peneloplay.setupUIWidgets()
@setupUIWidgets()
#clear any existing sounds
Peneloplay.stopPlaying()
@_player = soundManager.createSound(
@stopPlaying()
_player = soundManager.createSound
id: "pnp-current-sound"
url: @_src
whileloading: ->
percentageFinished = (@_player.bytesLoaded / @_player.bytesTotal) * 100
percentageWidth = (bounds.waveformWidth / 100) * percentageFinished
@ui.downloadOverlay.css "width", percentageWidth
url: _src
#Might not need the progress overlay if firefucks get their act together
whileloading: =>
if not soundManager.html5.mp3
percentageFinished = (_player.bytesLoaded / _player.bytesTotal) * 100
percentageWidth = (bounds.waveformWidth / 100) * percentageFinished
ui.downloadOverlay.css "width", percentageWidth
return
whileplaying: ->
percentageWidth = (bounds.waveformWidth / 100) * ((@_player.position / Peneloplay._getCalculatedDuration()) * 100)
@ui.playedOverlay.css "width", percentageWidth
@ui.timeElapsed.text _secondsToHms(@_player.position / 1000)
whileplaying: =>
percentageWidth = (bounds.waveformWidth / 100) * ((_player.position / @_getCalculatedDuration()) * 100)
ui.playedOverlay.css "width", percentageWidth
ui.timeElapsed.text _secondsToHms(_player.position / 1000)
return
)
@_player.play onplay: ->
args[0].success()
_player.play onplay: ->
args.success()
return
Peneloplay._hookupMouseEntryEvents()
@_hookupMouseEntryEvents()
return
pause: ->
@_player.pause() if @_player.playState is 1
_player.pause() if _player.playState is 1
return
resume: ->
@_player.resume() if @_player.paused
_player.resume() if _player.paused
return
getMixState: ->
if not @_player or @_player.playState is 0
if not _player or _player.playState is 0
return 0
else return 2 if @_player.paused
else return 2 if _player.paused
1
playLive: (args)->
Peneloplay.stopPlaying()
@_player = soundManager.createSound(
@stopPlaying()
_player = soundManager.createSound(
id: "com.podnoms.player-live"
url: com.podnoms.settings.liveStreamRoot
volume: 50
stream: true
useMovieStar: true
)
@_player.play onplay: ->
args[0].success()
_player.play onplay: ->
args.success()
return
return
stopLive: (args)->
Peneloplay.stopPlaying()
args[0].success()
@stopPlaying()
args.success()
return

View File

@@ -0,0 +1,44 @@
@social = do ->
postFacebookLike: (mixId) ->
#first off, find if the current user has allowed facebook likes
$.getJSON "social/like/" + mixId + "/", (data) ->
utils.showAlert "Posted your like to facebook, you can stop this in your settings page.", "Cheers feen"
generateEmbedCode: (model) ->
console.log("Generating embed code");
utils.modal "/dlg/embed/" + model.get('slug')
sharePageToTwitter: (model) ->
#We get the URL of the link
loc = $(this).attr("href")
#We get the title of the link
title = $(this).attr("title")
#We trigger a new window with the Twitter dialog, in the middle of the page
window.open "http://twitter.com/share?url=" + "http://" + window.location.host + "/" + model.get("item_url") + "&amp;text=" + model.get("title"), "twitterwindow", "height=450, width=550, top=" + ($(window).height() / 2 - 225) + ", left=" + $(window).width() / 2 + ", toolbar=0, location=0, menubar=0, directories=0, scrollbars=0"
sharePageToFacebook: (model) ->
FB.init({
appId : '154504534677009',
xfbml : true
});
FB.getLoginStatus (oResponse) ->
if oResponse.status is "connected"
FB.ui
method: "feed"
name: "Check out this mix on Deep South Sounds"
display: "iframe"
link: "http://" + window.location.host + "/" + model.get("item_url")
picture: model.get("mix_image")
caption: model.get("title")
description: model.get("description")
, (response) ->
if response and response.post_id
utils.showAlert "Success", "Post shared to facebook"
else
utils.showError "Error", "Failure sharing post"

View File

@@ -1,4 +1,20 @@
@utils = do ->
getCookie: (name) ->
cookieValue = null
if document.cookie and document.cookie isnt ""
cookies = document.cookie.split(";")
i = 0
while i < cookies.length
cookie = jQuery.trim(cookies[i])
# Does this cookie string begin with the name we want?
if cookie.substring(0, name.length + 1) is (name + "=")
cookieValue = decodeURIComponent(cookie.substring(name.length + 1))
break
i++
cookieValue
modal: (url) ->
return if $('#modal-header').length
if url
@@ -109,3 +125,87 @@
isMe: (id) ->
id == com.podnoms.settings.currentUser
$(document).ready ->
window.location.hash = "" if window.location.hash is "#_=_"
Backbone.history.navigate "/" if window.location.hash is "upload#"
unless Array::indexOf
console.log "Shimming indexOf for IE8"
Array::indexOf = (searchElement) -> #, fromIndex
"use strict"
throw new TypeError() unless this?
n = undefined
k = undefined
t = Object(this)
len = t.length >>> 0
return -1 if len is 0
n = 0
if arguments_.length > 1
n = Number(arguments_[1])
unless n is n # shortcut for verifying if it's NaN
n = 0
else n = (n > 0 or -1) * Math.floor(Math.abs(n)) if n isnt 0 and n isnt Infinity and n isnt -Infinity
return -1 if n >= len
k = (if n >= 0 then n else Math.max(len - Math.abs(n), 0))
while k < len
return k if k of t and t[k] is searchElement
k++
-1
return
$(document).ajaxSend (event, xhr, settings) ->
getCookie = (name) ->
cookieValue = null
if document.cookie and document.cookie isnt ""
cookies = document.cookie.split(";")
i = 0
while i < cookies.length
cookie = jQuery.trim(cookies[i])
# Does this cookie string begin with the name we want?
if cookie.substring(0, name.length + 1) is (name + "=")
cookieValue = decodeURIComponent(cookie.substring(name.length + 1))
break
i++
cookieValue
sameOrigin = (url) ->
# url could be relative or scheme relative or absolute
host = document.location.host # host + port
protocol = document.location.protocol
sr_origin = "//" + host
origin = protocol + sr_origin
# Allow absolute or scheme relative URLs to same origin
# or any other URL that isn't scheme relative or absolute i.e relative.
(url is origin or url.slice(0, origin.length + 1) is origin + "/") or (url is sr_origin or url.slice(0,
sr_origin.length + 1) is sr_origin + "/") or not (/^(\/\/|http:|https:).*/.test(url))
safeMethod = (method) ->
/^(GET|HEAD|OPTIONS|TRACE)$/.test method
xhr.setRequestHeader "X-CSRFToken", getCookie("csrftoken") if typeof (xhr.setRequestHeader) is typeof (Function) if not safeMethod(settings.type) and sameOrigin(settings.url)
return
$.ajaxSetup
beforeSend: (xhr, settings) ->
# Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader "X-CSRFToken", utils.getCookie("csrftoken") unless /^http:.*/.test(settings.url) or /^https:.*/.test(settings.url)
return
statusCode:
401: ->
vent.trigger "app:login"
window.location.replace "/"
return
403: ->
vent.trigger "app:denied"
window.location.replace "/"
return
unless com.podnoms.settings.isDebug
#console.log("Looking under the hood? Check us out on github https://github.com/fergalmoran/dss");
console = {}
console.log = (message) ->

View File

@@ -0,0 +1,25 @@
<div class="itemdiv dialogdiv">
<div class="user">
<img alt="avatar image" src="<%= user_image %>">
</div>
<div class="body">
<div class="time">
<i class="fa fa-clock-o"></i>
<span class="green"><%= human_date %></span>
</div>
<div class="name">
<a href="<%= user_profile %>"><%= user_name %></a>
</div>
<div class="text">
<%= verb %> <a href="<%= item_url %>"><%= item_name %></a>
</div>
<div class="tools">
<a href="#" class="btn btn-xser btn-info">
<i class="fa fa-only fa fa-share-alt"></i>
</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<div class="widget-header header-color-blue2">
<h4 class="lighter smaller">
<i class="fa fa-comment"></i>
Activity
</h4>
</div>
<div class="widget-body">
<div class="widget-main" id="activity-container">
</div>
</div>

View File

@@ -10,7 +10,7 @@
</div>
<div class="name">
<a href="#"><%= user_name %></a>
<a href="<%= user_url %>"><%= user_name %></a>
</div>
<div class="text"><%= comment %></div>

View File

@@ -43,14 +43,12 @@
<% if (com.podnoms.settings.currentUser != -1) { %>
<li class="light-blue">
<a data-toggle="dropdown" data-bypass="true" href="#" class="dropdown-toggle">
{% thumbnail user|avatar_image "36x36" crop="center" as im %}
<img id="nav-profile-image" class="nav-user-photo"
src="{{ im.url }}"
alt="AAProfileImg"/>
{% endthumbnail %}
<img id="nav-profile-image" class="nav-user-photo"
src="<%= com.podnoms.settings.avatarImage %>"
alt="AAProfileImg"/>
<span class="user-info">
<small>Welcome,</small>
{{ user.first_name }}
<%= com.podnoms.settings.userName %>
</span>
<i class="fa fa-caret-down"></i>
</a>
@@ -62,7 +60,7 @@
</a>
</li>
<li>
<a href="{{ user|profile_url }}">
<a href="<%= com.podnoms.settings.userUrl %>">
<i class="fa fa-user"></i>
Profile
</a>

View File

@@ -0,0 +1,149 @@
<div class="widget-box">
<div class="widget-header widget-header-blue widget-header-flat">
<h4 class="widget-title lighter">New Mix Wizard</h4>
<div class="widget-toolbar">
<label>
<small class="green">
<b>Keep it clean</b>
</small>
</label>
</div>
</div>
<div class="widget-body">
<div class="widget-main">
<div id="mix-upload-wizard" data-target="#step-container">
<ul class="wizard-steps">
<li data-target="#step1" class="active" id="header-step1">
<span class="step">1</span>
<span class="title">Mix</span>
</li>
<li data-target="#step2" id="header-step2">
<span class="step">2</span>
<span class="title">Details</span>
</li>
<li data-target="#step3" id="header-step3">
<span class="step">3</span>
<span class="title">Flair</span>
</li>
</ul>
</div>
<hr>
<div class="progress">
<div class="progress-bar" id="mix-upload-progress"></div>
</div>
<div class="step-content pos-rel" id="step-container">
<div class="step-pane active" id="step1">
<form action="_upload/" id="mix-upload-form" class="dropzone">
<input type="hidden" name="upload-hash">
<div class="fallback">
Your browser is too old for uploading file <br/>
Please come back with Chrome or Firefox
</div>
</form>
</div>
<div class="step-pane" id="step2">
<form id="mix-details-form" class="form-horizontal">
<input type="hidden" class="upload-hash">
<input type="hidden" id="upload-extension">
<div class="form-group">
<label class="control-label text-primary"
for="title">Title</label>
<input class="form-control" type="text"
id="title" name="title"
placeholder="Brief title for your mix"
value="<%= title %>">
</div>
<div class="form-group">
<label class="control-label text-primary"
for="description">Description</label>
<textarea class="autosize-transition form-control"
rows="12"
id="description" name="description"
placeholder="Tracklist or something.."><%= description %></textarea>
</div>
</form>
</div>
<div class="step-pane" id="step3">
<form id="mix-flair-form" class="form-horizontal">
<div class="row-fluid">
<div class="control-group">
<label for="genres" class="control-label text-primary">Genres</label>
<div class="controls">
<input value="0000" id="genres" style="width:98%"/>
</div>
</div>
</div>
<div class="row-fluid">
<h5 class="text-primary">Mix Image (click to change)</h5>
<div class="fileinput fileinput-new" data-provides="fileinput">
<div class="fileinput-preview thumbnail" data-trigger="fileinput"
style="width: 200px; height: 150px;">
<img id="mix-image"
class="editable img-responsive image-user-profile"
alt="Mix Image"
src="<%= mix_image %>">
</div>
<div>
<span class="btn btn-default btn-file">
<span class="fileinput-new">Select image</span>
<span class="fileinput-exists">Change</span>
<input type="file" name="..." id="mix-image-fileinput">
</span>
<a href="#" class="btn btn-default fileinput-exists"
data-dismiss="fileinput">Remove</a>
</div>
</div>
</div>
<div class="row-fluid">
<div class="mix_display_controls">
<label class="inline">
<small class="muted">Downloads:</small>
<input class="ace ace-switch ace-switch-7"
type="checkbox"
id="download_allowed"
name="download_allowed"
<% print(download_allowed ? 'checked="checked"' : '') %>
>
<span class="lbl"></span>
</label>
<% if (canHomepage()) { %>
<label class="inline">
<small class="muted">Homepage:</small>
<input class="ace ace-switch ace-switch-7"
type="checkbox"
id="is_featured"
name="is_featured"
<% print(is_featured ? 'checked="checked"' : '') %>
>
<span class="lbl"></span>
</label>
<% } %>
</div>
</div>
</form>
</div>
</div>
<hr>
<div class="wizard-actions">
<button class="btn btn-prev" disabled="disabled">
<i class="ace-icon fa fa-arrow-left"></i>
Prev
</button>
<button class="btn btn-success btn-next" data-last="Finish">
Next
<i class="ace-icon fa fa-arrow-right icon-on-right"></i>
</button>
</div>
</div>
</div>
</div>

View File

@@ -89,19 +89,13 @@
<a class="btn <% if (liked) { %>btn-pink<% } else { %>btn-light<% } %> btn-xs"
id="like-<%= id %>"
data-id="<%= id %>">
<i class="fa fa-heart"></i> Like
<% if (liked) { %>
d
<% } %>
<i class="fa fa-heart"></i> Like<%if (liked) {%>d<% } %>
</a>
</div>
<div class="favourite-button footer-button">
<a class="btn <% if (favourited) { %>btn-pink<% } else { %>btn-light<% } %> btn-xs"
id="favourite-<%= id %>"
data-id="<%= id %>"><i class="fa fa-star-empty"></i> Favourite
<% if (favourited) { %>
d
<% } %>
data-id="<%= id %>"><i class="fa fa-star-empty"></i> Favourite<% if (favourited) {%>d<% } %>
</a>
</div>
<% } %>

View File

@@ -0,0 +1,13 @@
<a href="<%= notification_url %>">
<img src="<%= user_image %>" class="msg-photo" alt="Avatar Image"/>
<span class="msg-body">
<span class="msg-title">
<span class="blue"><%= user_name %></span>
<%= verb %> <%= target %>
</span>
<span class="msg-time">
<i class="fa fa-clock-o"></i>
<span><%= humanise(date) %></span>
</span>
</span>
</a>

View File

@@ -0,0 +1,11 @@
<a data-toggle="dropdown" data-bypass="true" class="dropdown-toggle" href="#" id="notifications-dropdown">
<i id="notification-icon" class="fa fa-bell"></i>
<span class="badge badge-important" id="notification-count-badge"></span>
</a>
<ul class="pull-right dropdown-navbar dropdown-menu dropdown-caret dropdown-close" id="notif_list_node">
<li class="dropdown-header" >
<i class="fa fa-warning-sign"></i>
<span id="notification-count"></span>
</li>
</ul>

View File

@@ -0,0 +1,30 @@
<div class="widget-header header-color-pink">
<h5>
<i class="fa fa-music"></i>
Now Playing
</h5>
</div>
<div class="widget-body">
<div class="profile-activity clearfix">
<div>
<img class="pull-left" alt="mix-image" src="<%= mix_image %>">
<a class="user" href="<%= item_url %>" title="<%= title %>"><%= title %></a>
<div class="time">
<i class="fa fa-user bigger-110"></i>
<a href="/<%= user_profile_url %>">
<%= user_name %>
</a>
</div>
</div>
<div class="now-playing-tools action-buttons">
<a id="now-playing-play" class="blue" data-bypass="true">
<i class="fa fa-play bigger-125"></i>
</a>
<a id="now-playing-pause" class="red" data-bypass="true">
<i class="fa fa-pause bigger-125"></i>
</a>
</div>
</div>
</div>

View File

@@ -6,7 +6,7 @@
<i class="fa fa-search nav-search-icon"></i>
</span>
</form>
<ul id="suggestions" class="pull-right dropdown-navbar dropdown-menu dropdown-caret dropdown-close">
<ul id="search-results" class="pull-right dropdown-navbar dropdown-menu dropdown-caret dropdown-close">
<li class="dropdown-header">
<i class="fa fa-music"></i>
Mixes

View File

@@ -0,0 +1,16 @@
<li>
<a href="<%= item_url %>">
<img src="<%= mix_image %>" class="msg-photo" alt="<%= title %>">
<span class="msg-body">
<span class="msg-title">
<span class="blue"><%= user_name %>:</span>
<%= title %>
</span>
<span class="msg-time">
<i class="icon-time"></i>
<span><%= moment(upload_date).fromNow() %></span>
</span>
</span>
</a>
</li>

View File

@@ -0,0 +1,4 @@
<div class="widget-box" id="sidebar-now-playing"></div>
<div id="sidebar-advert">
</div>
<div id="sidebar-stream-content"></div>

View File

@@ -49,46 +49,25 @@
<div class="space-6"></div>
<div class="profile-contact-info">
<div class="profile-contact-links align-left">
{% if user.is_authenticated %}
<% if (utils.isAuth()) {%>
<a id="follow-button" class="btn btn-link <% if (is_following) { %> following-disabled <% } %>" data-bypass="true">
<i class="fa fa-plus-sign bigger-120 <% if (is_following) { %>red<% } else { %>green <%}%>"></i>
<% if (is_following) { %>Unfollow<% } else { %>Follow<%}%>
</a>
{% else %}
<% }else { %>
<a id="follow-button-login" class="btn btn-link" data-bypass="true">
<i class="fa fa-plus-sign bigger-120 green"></i>
Login to follow
</a>
{% endif %}
<% }%>
<br>
{% comment %}
<a class="btn btn-link" href="#">
<i class="fa fa-envelope bigger-120 pink"></i>
Send a message
</a>
{% endcomment %}
<a class="btn btn-link" href="<%= url %>">
<i class="fa fa-globe bigger-125 blue"></i>
Profile Link
</a>
</div>
<div class="space-6"></div>
{% comment %}
<div class="profile-social-links center">
<a href="#" class="tooltip-info" title="" data-original-title="Visit my Facebook">
<i class="middle fa fa-facebook-sign fa fa-2x blue"></i>
</a>
<a href="#" class="tooltip-info" title="" data-original-title="Visit my Twitter">
<i class="middle fa fa-twitter-sign fa fa-2x light-blue"></i>
</a>
<a href="#" class="tooltip-error" title="" data-original-title="Visit my Pinterest">
<i class="middle fa fa-pinterest-sign fa fa-2x red"></i>
</a>
</div>
{% endcomment %}
</div>
</div>

View File

@@ -46,6 +46,7 @@
}
this.$stepContainer = $(this.$element.data('target') || 'body');//ACE
console.log(this.$stepContainer);
};
Wizard.prototype = {

View File

@@ -2,478 +2,468 @@
// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
// http://github.com/derickbailey/backbone.syphon
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['underscore', "jquery", "backbone"], factory);
}
}(this, function (_, jQuery, Backbone) {
Backbone.Syphon = (function(Backbone, $, _){
var Syphon = {};
Backbone.Syphon = (function(Backbone, $, _){
var Syphon = {};
// Ignore Element Types
// --------------------
// Ignore Element Types
// --------------------
// Tell Syphon to ignore all elements of these types. You can
// push new types to ignore directly in to this array.
Syphon.ignoredTypes = ["button", "submit", "reset", "fieldset"];
// Tell Syphon to ignore all elements of these types. You can
// push new types to ignore directly in to this array.
Syphon.ignoredTypes = ["button", "submit", "reset", "fieldset"];
// Syphon
// ------
// Syphon
// ------
// Get a JSON object that represents
// all of the form inputs, in this view.
// Alternately, pass a form element directly
// in place of the view.
Syphon.serialize = function(view, options){
var data = {};
// Get a JSON object that represents
// all of the form inputs, in this view.
// Alternately, pass a form element directly
// in place of the view.
Syphon.serialize = function(view, options){
var data = {};
// Build the configuration
var config = buildConfig(options);
// Build the configuration
var config = buildConfig(options);
// Get all of the elements to process
var elements = getInputElements(view, config);
// Get all of the elements to process
var elements = getInputElements(view, config);
// Process all of the elements
_.each(elements, function(el){
var $el = $(el);
var type = getElementType($el);
// Get the key for the input
var keyExtractor = config.keyExtractors.get(type);
var key = keyExtractor($el);
// Get the value for the input
var inputReader = config.inputReaders.get(type);
var value = inputReader($el);
// Get the key assignment validator and make sure
// it's valid before assigning the value to the key
var validKeyAssignment = config.keyAssignmentValidators.get(type);
if (validKeyAssignment($el, key, value)){
var keychain = config.keySplitter(key);
data = assignKeyValue(data, keychain, value);
}
});
// Done; send back the results.
return data;
};
// Use the given JSON object to populate
// all of the form inputs, in this view.
// Alternately, pass a form element directly
// in place of the view.
Syphon.deserialize = function(view, data, options){
// Build the configuration
var config = buildConfig(options);
// Get all of the elements to process
var elements = getInputElements(view, config);
// Flatten the data structure that we are deserializing
var flattenedData = flattenData(config, data);
// Process all of the elements
_.each(elements, function(el){
var $el = $(el);
var type = getElementType($el);
// Get the key for the input
var keyExtractor = config.keyExtractors.get(type);
var key = keyExtractor($el);
// Get the input writer and the value to write
var inputWriter = config.inputWriters.get(type);
var value = flattenedData[key];
// Write the value to the input
inputWriter($el, value);
});
};
// Helpers
// -------
// Retrieve all of the form inputs
// from the form
var getInputElements = function(view, config){
var form = getForm(view);
var elements = form.elements;
elements = _.reject(elements, function(el){
var reject;
var type = getElementType(el);
var extractor = config.keyExtractors.get(type);
var identifier = extractor($(el));
var foundInIgnored = _.include(config.ignoredTypes, type);
var foundInInclude = _.include(config.include, identifier);
var foundInExclude = _.include(config.exclude, identifier);
if (foundInInclude){
reject = false;
} else {
if (config.include){
reject = true;
} else {
reject = (foundInExclude || foundInIgnored);
}
}
return reject;
});
return elements;
};
// Determine what type of element this is. It
// will either return the `type` attribute of
// an `<input>` element, or the `tagName` of
// the element when the element is not an `<input>`.
var getElementType = function(el){
var typeAttr;
// Process all of the elements
_.each(elements, function(el){
var $el = $(el);
var tagName = $el[0].tagName;
var type = tagName;
var type = getElementType($el);
if (tagName.toLowerCase() === "input"){
typeAttr = $el.attr("type");
if (typeAttr){
type = typeAttr;
} else {
type = "text";
}
// Get the key for the input
var keyExtractor = config.keyExtractors.get(type);
var key = keyExtractor($el);
// Get the value for the input
var inputReader = config.inputReaders.get(type);
var value = inputReader($el);
// Get the key assignment validator and make sure
// it's valid before assigning the value to the key
var validKeyAssignment = config.keyAssignmentValidators.get(type);
if (validKeyAssignment($el, key, value)){
var keychain = config.keySplitter(key);
data = assignKeyValue(data, keychain, value);
}
});
// Always return the type as lowercase
// so it can be matched to lowercase
// type registrations.
return type.toLowerCase();
};
// If a form element is given, just return it.
// Otherwise, get the form element from the view.
var getForm = function(viewOrForm){
if (_.isUndefined(viewOrForm.$el) && viewOrForm.tagName.toLowerCase() === 'form'){
return viewOrForm;
} else {
return viewOrForm.$el.is("form") ? viewOrForm.el : viewOrForm.$("form")[0];
}
};
// Build a configuration object and initialize
// default values.
var buildConfig = function(options){
var config = _.clone(options) || {};
config.ignoredTypes = _.clone(Syphon.ignoredTypes);
config.inputReaders = config.inputReaders || Syphon.InputReaders;
config.inputWriters = config.inputWriters || Syphon.InputWriters;
config.keyExtractors = config.keyExtractors || Syphon.KeyExtractors;
config.keySplitter = config.keySplitter || Syphon.KeySplitter;
config.keyJoiner = config.keyJoiner || Syphon.KeyJoiner;
config.keyAssignmentValidators = config.keyAssignmentValidators || Syphon.KeyAssignmentValidators;
return config;
};
// Assigns `value` to a parsed JSON key.
//
// The first parameter is the object which will be
// modified to store the key/value pair.
//
// The second parameter accepts an array of keys as a
// string with an option array containing a
// single string as the last option.
//
// The third parameter is the value to be assigned.
//
// Examples:
//
// `["foo", "bar", "baz"] => {foo: {bar: {baz: "value"}}}`
//
// `["foo", "bar", ["baz"]] => {foo: {bar: {baz: ["value"]}}}`
//
// When the final value is an array with a string, the key
// becomes an array, and values are pushed in to the array,
// allowing multiple fields with the same name to be
// assigned to the array.
var assignKeyValue = function(obj, keychain, value) {
if (!keychain){ return obj; }
var key = keychain.shift();
// build the current object we need to store data
if (!obj[key]){
obj[key] = _.isArray(key) ? [] : {};
}
// if it's the last key in the chain, assign the value directly
if (keychain.length === 0){
if (_.isArray(obj[key])){
obj[key].push(value);
} else {
obj[key] = value;
}
}
// recursive parsing of the array, depth-first
if (keychain.length > 0){
assignKeyValue(obj[key], keychain, value);
}
return obj;
};
// Flatten the data structure in to nested strings, using the
// provided `KeyJoiner` function.
//
// Example:
//
// This input:
//
// ```js
// {
// widget: "wombat",
// foo: {
// bar: "baz",
// baz: {
// quux: "qux"
// },
// quux: ["foo", "bar"]
// }
// }
// ```
//
// With a KeyJoiner that uses [ ] square brackets,
// should produce this output:
//
// ```js
// {
// "widget": "wombat",
// "foo[bar]": "baz",
// "foo[baz][quux]": "qux",
// "foo[quux]": ["foo", "bar"]
// }
// ```
var flattenData = function(config, data, parentKey){
var flatData = {};
_.each(data, function(value, keyName){
var hash = {};
// If there is a parent key, join it with
// the current, child key.
if (parentKey){
keyName = config.keyJoiner(parentKey, keyName);
}
if (_.isArray(value)){
keyName += "[]";
hash[keyName] = value;
} else if (_.isObject(value)){
hash = flattenData(config, value, keyName);
} else {
hash[keyName] = value;
}
// Store the resulting key/value pairs in the
// final flattened data object
_.extend(flatData, hash);
});
return flatData;
};
return Syphon;
})(Backbone, jQuery, _);
// Type Registry
// -------------
// Type Registries allow you to register something to
// an input type, and retrieve either the item registered
// for a specific type or the default registration
Backbone.Syphon.TypeRegistry = function(){
this.registeredTypes = {};
// Done; send back the results.
return data;
};
// Borrow Backbone's `extend` keyword for our TypeRegistry
Backbone.Syphon.TypeRegistry.extend = Backbone.Model.extend;
// Use the given JSON object to populate
// all of the form inputs, in this view.
// Alternately, pass a form element directly
// in place of the view.
Syphon.deserialize = function(view, data, options){
// Build the configuration
var config = buildConfig(options);
_.extend(Backbone.Syphon.TypeRegistry.prototype, {
// Get all of the elements to process
var elements = getInputElements(view, config);
// Get the registered item by type. If nothing is
// found for the specified type, the default is
// returned.
get: function(type){
var item = this.registeredTypes[type];
// Flatten the data structure that we are deserializing
var flattenedData = flattenData(config, data);
if (!item){
item = this.registeredTypes["default"];
// Process all of the elements
_.each(elements, function(el){
var $el = $(el);
var type = getElementType($el);
// Get the key for the input
var keyExtractor = config.keyExtractors.get(type);
var key = keyExtractor($el);
// Get the input writer and the value to write
var inputWriter = config.inputWriters.get(type);
var value = flattenedData[key];
// Write the value to the input
inputWriter($el, value);
});
};
// Helpers
// -------
// Retrieve all of the form inputs
// from the form
var getInputElements = function(view, config){
var form = getForm(view);
var elements = form.elements;
elements = _.reject(elements, function(el){
var reject;
var type = getElementType(el);
var extractor = config.keyExtractors.get(type);
var identifier = extractor($(el));
var foundInIgnored = _.include(config.ignoredTypes, type);
var foundInInclude = _.include(config.include, identifier);
var foundInExclude = _.include(config.exclude, identifier);
if (foundInInclude){
reject = false;
} else {
if (config.include){
reject = true;
} else {
reject = (foundInExclude || foundInIgnored);
}
}
return item;
},
return reject;
});
// Register a new item for a specified type
register: function(type, item){
this.registeredTypes[type] = item;
},
return elements;
};
// Register a default item to be used when no
// item for a specified type is found
registerDefault: function(item){
this.registeredTypes["default"] = item;
},
// Determine what type of element this is. It
// will either return the `type` attribute of
// an `<input>` element, or the `tagName` of
// the element when the element is not an `<input>`.
var getElementType = function(el){
var typeAttr;
var $el = $(el);
var tagName = $el[0].tagName;
var type = tagName;
// Remove an item from a given type registration
unregister: function(type){
if (this.registeredTypes[type]){
delete this.registeredTypes[type];
if (tagName.toLowerCase() === "input"){
typeAttr = $el.attr("type");
if (typeAttr){
type = typeAttr;
} else {
type = "text";
}
}
});
// Always return the type as lowercase
// so it can be matched to lowercase
// type registrations.
return type.toLowerCase();
};
// If a form element is given, just return it.
// Otherwise, get the form element from the view.
var getForm = function(viewOrForm){
if (_.isUndefined(viewOrForm.$el) && viewOrForm.tagName.toLowerCase() === 'form'){
return viewOrForm;
} else {
return viewOrForm.$el.is("form") ? viewOrForm.el : viewOrForm.$("form")[0];
}
};
// Build a configuration object and initialize
// default values.
var buildConfig = function(options){
var config = _.clone(options) || {};
// Key Extractors
// --------------
config.ignoredTypes = _.clone(Syphon.ignoredTypes);
config.inputReaders = config.inputReaders || Syphon.InputReaders;
config.inputWriters = config.inputWriters || Syphon.InputWriters;
config.keyExtractors = config.keyExtractors || Syphon.KeyExtractors;
config.keySplitter = config.keySplitter || Syphon.KeySplitter;
config.keyJoiner = config.keyJoiner || Syphon.KeyJoiner;
config.keyAssignmentValidators = config.keyAssignmentValidators || Syphon.KeyAssignmentValidators;
// Key extractors produce the "key" in `{key: "value"}`
// pairs, when serializing.
Backbone.Syphon.KeyExtractorSet = Backbone.Syphon.TypeRegistry.extend();
return config;
};
// Built-in Key Extractors
Backbone.Syphon.KeyExtractors = new Backbone.Syphon.KeyExtractorSet();
// The default key extractor, which uses the
// input element's "id" attribute
Backbone.Syphon.KeyExtractors.registerDefault(function($el){
return $el.prop("name");
});
// Input Readers
// -------------
// Input Readers are used to extract the value from
// an input element, for the serialized object result
Backbone.Syphon.InputReaderSet = Backbone.Syphon.TypeRegistry.extend();
// Built-in Input Readers
Backbone.Syphon.InputReaders = new Backbone.Syphon.InputReaderSet();
// The default input reader, which uses an input
// element's "value"
Backbone.Syphon.InputReaders.registerDefault(function($el){
return $el.val();
});
// Checkbox reader, returning a boolean value for
// whether or not the checkbox is checked.
Backbone.Syphon.InputReaders.register("checkbox", function($el){
var checked = $el.prop("checked");
return checked;
});
// Input Writers
// -------------
// Input Writers are used to insert a value from an
// object into an input element.
Backbone.Syphon.InputWriterSet = Backbone.Syphon.TypeRegistry.extend();
// Built-in Input Writers
Backbone.Syphon.InputWriters = new Backbone.Syphon.InputWriterSet();
// The default input writer, which sets an input
// element's "value"
Backbone.Syphon.InputWriters.registerDefault(function($el, value){
$el.val(value);
});
// Checkbox writer, set whether or not the checkbox is checked
// depending on the boolean value.
Backbone.Syphon.InputWriters.register("checkbox", function($el, value){
$el.prop("checked", value);
});
// Radio button writer, set whether or not the radio button is
// checked. The button should only be checked if it's value
// equals the given value.
Backbone.Syphon.InputWriters.register("radio", function($el, value){
$el.prop("checked", $el.val() === value);
});
// Key Assignment Validators
// -------------------------
// Key Assignment Validators are used to determine whether or not a
// key should be assigned to a value, after the key and value have been
// extracted from the element. This is the last opportunity to prevent
// bad data from getting serialized to your object.
Backbone.Syphon.KeyAssignmentValidatorSet = Backbone.Syphon.TypeRegistry.extend();
// Build-in Key Assignment Validators
Backbone.Syphon.KeyAssignmentValidators = new Backbone.Syphon.KeyAssignmentValidatorSet();
// Everything is valid by default
Backbone.Syphon.KeyAssignmentValidators.registerDefault(function(){ return true; });
// But only the "checked" radio button for a given
// radio button group is valid
Backbone.Syphon.KeyAssignmentValidators.register("radio", function($el, key, value){
return $el.prop("checked");
});
// Backbone.Syphon.KeySplitter
// ---------------------------
// This function is used to split DOM element keys in to an array
// of parts, which are then used to create a nested result structure.
// returning `["foo", "bar"]` results in `{foo: { bar: "value" }}`.
// Assigns `value` to a parsed JSON key.
//
// Override this method to use a custom key splitter, such as:
// `<input name="foo.bar.baz">`, `return key.split(".")`
Backbone.Syphon.KeySplitter = function(key){
var matches = key.match(/[^\[\]]+/g);
// The first parameter is the object which will be
// modified to store the key/value pair.
//
// The second parameter accepts an array of keys as a
// string with an option array containing a
// single string as the last option.
//
// The third parameter is the value to be assigned.
//
// Examples:
//
// `["foo", "bar", "baz"] => {foo: {bar: {baz: "value"}}}`
//
// `["foo", "bar", ["baz"]] => {foo: {bar: {baz: ["value"]}}}`
//
// When the final value is an array with a string, the key
// becomes an array, and values are pushed in to the array,
// allowing multiple fields with the same name to be
// assigned to the array.
var assignKeyValue = function(obj, keychain, value) {
if (!keychain){ return obj; }
if (key.indexOf("[]") === key.length - 2){
lastKey = matches.pop();
matches.push([lastKey]);
var key = keychain.shift();
// build the current object we need to store data
if (!obj[key]){
obj[key] = _.isArray(key) ? [] : {};
}
return matches;
}
// if it's the last key in the chain, assign the value directly
if (keychain.length === 0){
if (_.isArray(obj[key])){
obj[key].push(value);
} else {
obj[key] = value;
}
}
// recursive parsing of the array, depth-first
if (keychain.length > 0){
assignKeyValue(obj[key], keychain, value);
}
// Backbone.Syphon.KeyJoiner
// -------------------------
return obj;
};
// Take two segments of a key and join them together, to create the
// de-normalized key name, when deserializing a data structure back
// in to a form.
// Flatten the data structure in to nested strings, using the
// provided `KeyJoiner` function.
//
// Example:
//
// With this data strucutre `{foo: { bar: {baz: "value", quux: "another"} } }`,
// the key joiner will be called with these parameters, and assuming the
// join happens with "[ ]" square brackets, the specified output:
// This input:
//
// `KeyJoiner("foo", "bar")` //=> "foo[bar]"
// `KeyJoiner("foo[bar]", "baz")` //=> "foo[bar][baz]"
// `KeyJoiner("foo[bar]", "quux")` //=> "foo[bar][quux]"
// ```js
// {
// widget: "wombat",
// foo: {
// bar: "baz",
// baz: {
// quux: "qux"
// },
// quux: ["foo", "bar"]
// }
// }
// ```
//
// With a KeyJoiner that uses [ ] square brackets,
// should produce this output:
//
// ```js
// {
// "widget": "wombat",
// "foo[bar]": "baz",
// "foo[baz][quux]": "qux",
// "foo[quux]": ["foo", "bar"]
// }
// ```
var flattenData = function(config, data, parentKey){
var flatData = {};
Backbone.Syphon.KeyJoiner = function(parentKey, childKey){
return parentKey + "[" + childKey + "]";
_.each(data, function(value, keyName){
var hash = {};
// If there is a parent key, join it with
// the current, child key.
if (parentKey){
keyName = config.keyJoiner(parentKey, keyName);
}
if (_.isArray(value)){
keyName += "[]";
hash[keyName] = value;
} else if (_.isObject(value)){
hash = flattenData(config, value, keyName);
} else {
hash[keyName] = value;
}
// Store the resulting key/value pairs in the
// final flattened data object
_.extend(flatData, hash);
});
return flatData;
};
return Syphon;
})(Backbone, jQuery, _);
// Type Registry
// -------------
// Type Registries allow you to register something to
// an input type, and retrieve either the item registered
// for a specific type or the default registration
Backbone.Syphon.TypeRegistry = function(){
this.registeredTypes = {};
};
// Borrow Backbone's `extend` keyword for our TypeRegistry
Backbone.Syphon.TypeRegistry.extend = Backbone.Model.extend;
_.extend(Backbone.Syphon.TypeRegistry.prototype, {
// Get the registered item by type. If nothing is
// found for the specified type, the default is
// returned.
get: function(type){
var item = this.registeredTypes[type];
if (!item){
item = this.registeredTypes["default"];
}
return item;
},
// Register a new item for a specified type
register: function(type, item){
this.registeredTypes[type] = item;
},
// Register a default item to be used when no
// item for a specified type is found
registerDefault: function(item){
this.registeredTypes["default"] = item;
},
// Remove an item from a given type registration
unregister: function(type){
if (this.registeredTypes[type]){
delete this.registeredTypes[type];
}
}
});
// Key Extractors
// --------------
// Key extractors produce the "key" in `{key: "value"}`
// pairs, when serializing.
Backbone.Syphon.KeyExtractorSet = Backbone.Syphon.TypeRegistry.extend();
// Built-in Key Extractors
Backbone.Syphon.KeyExtractors = new Backbone.Syphon.KeyExtractorSet();
// The default key extractor, which uses the
// input element's "id" attribute
Backbone.Syphon.KeyExtractors.registerDefault(function($el){
return $el.prop("name");
});
// Input Readers
// -------------
// Input Readers are used to extract the value from
// an input element, for the serialized object result
Backbone.Syphon.InputReaderSet = Backbone.Syphon.TypeRegistry.extend();
// Built-in Input Readers
Backbone.Syphon.InputReaders = new Backbone.Syphon.InputReaderSet();
// The default input reader, which uses an input
// element's "value"
Backbone.Syphon.InputReaders.registerDefault(function($el){
return $el.val();
});
// Checkbox reader, returning a boolean value for
// whether or not the checkbox is checked.
Backbone.Syphon.InputReaders.register("checkbox", function($el){
var checked = $el.prop("checked");
return checked;
});
// Input Writers
// -------------
// Input Writers are used to insert a value from an
// object into an input element.
Backbone.Syphon.InputWriterSet = Backbone.Syphon.TypeRegistry.extend();
// Built-in Input Writers
Backbone.Syphon.InputWriters = new Backbone.Syphon.InputWriterSet();
// The default input writer, which sets an input
// element's "value"
Backbone.Syphon.InputWriters.registerDefault(function($el, value){
$el.val(value);
});
// Checkbox writer, set whether or not the checkbox is checked
// depending on the boolean value.
Backbone.Syphon.InputWriters.register("checkbox", function($el, value){
$el.prop("checked", value);
});
// Radio button writer, set whether or not the radio button is
// checked. The button should only be checked if it's value
// equals the given value.
Backbone.Syphon.InputWriters.register("radio", function($el, value){
$el.prop("checked", $el.val() === value);
});
// Key Assignment Validators
// -------------------------
// Key Assignment Validators are used to determine whether or not a
// key should be assigned to a value, after the key and value have been
// extracted from the element. This is the last opportunity to prevent
// bad data from getting serialized to your object.
Backbone.Syphon.KeyAssignmentValidatorSet = Backbone.Syphon.TypeRegistry.extend();
// Build-in Key Assignment Validators
Backbone.Syphon.KeyAssignmentValidators = new Backbone.Syphon.KeyAssignmentValidatorSet();
// Everything is valid by default
Backbone.Syphon.KeyAssignmentValidators.registerDefault(function(){ return true; });
// But only the "checked" radio button for a given
// radio button group is valid
Backbone.Syphon.KeyAssignmentValidators.register("radio", function($el, key, value){
return $el.prop("checked");
});
// Backbone.Syphon.KeySplitter
// ---------------------------
// This function is used to split DOM element keys in to an array
// of parts, which are then used to create a nested result structure.
// returning `["foo", "bar"]` results in `{foo: { bar: "value" }}`.
//
// Override this method to use a custom key splitter, such as:
// `<input name="foo.bar.baz">`, `return key.split(".")`
Backbone.Syphon.KeySplitter = function(key){
var matches = key.match(/[^\[\]]+/g);
if (key.indexOf("[]") === key.length - 2){
lastKey = matches.pop();
matches.push([lastKey]);
}
return matches;
}
return Backbone.Syphon;
}));
// Backbone.Syphon.KeyJoiner
// -------------------------
// Take two segments of a key and join them together, to create the
// de-normalized key name, when deserializing a data structure back
// in to a form.
//
// Example:
//
// With this data strucutre `{foo: { bar: {baz: "value", quux: "another"} } }`,
// the key joiner will be called with these parameters, and assuming the
// join happens with "[ ]" square brackets, the specified output:
//
// `KeyJoiner("foo", "bar")` //=> "foo[bar]"
// `KeyJoiner("foo[bar]", "baz")` //=> "foo[bar][baz]"
// `KeyJoiner("foo[bar]", "quux")` //=> "foo[bar][quux]"
Backbone.Syphon.KeyJoiner = function(parentKey, childKey){
return parentKey + "[" + childKey + "]";
}

View File

@@ -0,0 +1,198 @@
/* ===========================================================
* Bootstrap: fileinput.js v3.1.3
* http://jasny.github.com/bootstrap/javascript/#fileinput
* ===========================================================
* Copyright 2012-2014 Arnold Daniels
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
+function ($) { "use strict";
var isIE = window.navigator.appName == 'Microsoft Internet Explorer'
// FILEUPLOAD PUBLIC CLASS DEFINITION
// =================================
var Fileinput = function (element, options) {
this.$element = $(element)
this.$input = this.$element.find(':file')
if (this.$input.length === 0) return
this.name = this.$input.attr('name') || options.name
this.$hidden = this.$element.find('input[type=hidden][name="' + this.name + '"]')
if (this.$hidden.length === 0) {
this.$hidden = $('<input type="hidden">').insertBefore(this.$input)
}
this.$preview = this.$element.find('.fileinput-preview')
var height = this.$preview.css('height')
if (this.$preview.css('display') !== 'inline' && height !== '0px' && height !== 'none') {
this.$preview.css('line-height', height)
}
this.original = {
exists: this.$element.hasClass('fileinput-exists'),
preview: this.$preview.html(),
hiddenVal: this.$hidden.val()
}
this.listen()
}
Fileinput.prototype.listen = function() {
this.$input.on('change.bs.fileinput', $.proxy(this.change, this))
$(this.$input[0].form).on('reset.bs.fileinput', $.proxy(this.reset, this))
this.$element.find('[data-trigger="fileinput"]').on('click.bs.fileinput', $.proxy(this.trigger, this))
this.$element.find('[data-dismiss="fileinput"]').on('click.bs.fileinput', $.proxy(this.clear, this))
},
Fileinput.prototype.change = function(e) {
var files = e.target.files === undefined ? (e.target && e.target.value ? [{ name: e.target.value.replace(/^.+\\/, '')}] : []) : e.target.files
e.stopPropagation()
if (files.length === 0) {
this.clear()
return
}
this.$hidden.val('')
this.$hidden.attr('name', '')
this.$input.attr('name', this.name)
var file = files[0]
if (this.$preview.length > 0 && (typeof file.type !== "undefined" ? file.type.match(/^image\/(gif|png|jpeg)$/) : file.name.match(/\.(gif|png|jpe?g)$/i)) && typeof FileReader !== "undefined") {
var reader = new FileReader()
var preview = this.$preview
var element = this.$element
reader.onload = function(re) {
var $img = $('<img>')
$img[0].src = re.target.result
files[0].result = re.target.result
element.find('.fileinput-filename').text(file.name)
// if parent has max-height, using `(max-)height: 100%` on child doesn't take padding and border into account
if (preview.css('max-height') != 'none') $img.css('max-height', parseInt(preview.css('max-height'), 10) - parseInt(preview.css('padding-top'), 10) - parseInt(preview.css('padding-bottom'), 10) - parseInt(preview.css('border-top'), 10) - parseInt(preview.css('border-bottom'), 10))
preview.html($img)
element.addClass('fileinput-exists').removeClass('fileinput-new')
element.trigger('change.bs.fileinput', files)
}
reader.readAsDataURL(file)
} else {
this.$element.find('.fileinput-filename').text(file.name)
this.$preview.text(file.name)
this.$element.addClass('fileinput-exists').removeClass('fileinput-new')
this.$element.trigger('change.bs.fileinput')
}
},
Fileinput.prototype.clear = function(e) {
if (e) e.preventDefault()
this.$hidden.val('')
this.$hidden.attr('name', this.name)
this.$input.attr('name', '')
//ie8+ doesn't support changing the value of input with type=file so clone instead
if (isIE) {
var inputClone = this.$input.clone(true);
this.$input.after(inputClone);
this.$input.remove();
this.$input = inputClone;
} else {
this.$input.val('')
}
this.$preview.html('')
this.$element.find('.fileinput-filename').text('')
this.$element.addClass('fileinput-new').removeClass('fileinput-exists')
if (e !== undefined) {
this.$input.trigger('change')
this.$element.trigger('clear.bs.fileinput')
}
},
Fileinput.prototype.reset = function() {
this.clear()
this.$hidden.val(this.original.hiddenVal)
this.$preview.html(this.original.preview)
this.$element.find('.fileinput-filename').text('')
if (this.original.exists) this.$element.addClass('fileinput-exists').removeClass('fileinput-new')
else this.$element.addClass('fileinput-new').removeClass('fileinput-exists')
this.$element.trigger('reset.bs.fileinput')
},
Fileinput.prototype.trigger = function(e) {
this.$input.trigger('click')
e.preventDefault()
}
// FILEUPLOAD PLUGIN DEFINITION
// ===========================
var old = $.fn.fileinput
$.fn.fileinput = function (options) {
return this.each(function () {
var $this = $(this),
data = $this.data('bs.fileinput')
if (!data) $this.data('bs.fileinput', (data = new Fileinput(this, options)))
if (typeof options == 'string') data[options]()
})
}
$.fn.fileinput.Constructor = Fileinput
// FILEINPUT NO CONFLICT
// ====================
$.fn.fileinput.noConflict = function () {
$.fn.fileinput = old
return this
}
// FILEUPLOAD DATA-API
// ==================
$(document).on('click.fileinput.data-api', '[data-provides="fileinput"]', function (e) {
var $this = $(this)
if ($this.data('bs.fileinput')) return
$this.fileinput($this.data())
var $target = $(e.target).closest('[data-dismiss="fileinput"],[data-trigger="fileinput"]');
if ($target.length > 0) {
e.preventDefault()
$target.trigger('click.bs.fileinput')
}
})
}(window.jQuery);

View File

@@ -99,7 +99,10 @@ define(["jquery", "soundmanager2"], function ($, soundManager) {
ui.timeDuration.show();
ui.timeDuration.animate({ top: ui.waveform.position().top, left: (ui.waveform.position().left + ui.waveform.width()) - ui.timeDuration.width() });
ui.downloadOverlay.animate({top: ui.waveform.position().top, left: ui.waveform.position().left, height: ui.waveform.height()});
if soundManager.html5.mp3
ui.downloadOverlay.hide()
else
ui.downloadOverlay.animate({top: ui.waveform.position().top, left: ui.waveform.position().left, height: ui.waveform.height()});
ui.playedOverlay.show();
ui.playedOverlay.animate({top: ui.waveform.position().top, left: ui.waveform.position().left, height: ui.waveform.height()});

View File

@@ -70,8 +70,8 @@
<script src="//connect.facebook.net/en_US/all.js"></script>
<!--- start RemoveAMD -->
{% compressed_js 'backbone' %}
{% compressed_js 'lib' %}
{% compressed_js 'backbone' %}
{% compressed_js 'site' %}
{% compressed_js 'templates' %}

View File

@@ -16,7 +16,9 @@ com.podnoms.settings = {
staticUrl: '{{ STATIC_URL }}',
urlArgs: {{ IS_DEBUG }} ? "" : "bust="+ (new Date()).getTime(),
currentUser: {{ CURRENT_USER_ID }},
canHomepage: "{{ CURRENT_USER_CANHOMEPAGE }}",
userName: "{{ CURRENT_USER_NAME }}",
userSlug: "{{ CURRENT_USER_SLUG }}",
userUrl: "{{ CURRENT_USER_URL }}",
avatarImage: "{{ AVATAR_IMAGE }}"
avatarImage: "{{ AVATAR_IMAGE }}",
};