mirror of
https://github.com/fergalmoran/dss.git
synced 2025-12-22 09:38:18 +00:00
Initial go live commit
This commit is contained in:
@@ -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',
|
||||
|
||||
17
spa/ajax.py
17
spa/ajax.py
@@ -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)
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -164,4 +164,3 @@ define ['app.lib/dssView', 'utils', 'ace-editable', 'typeahead'],
|
||||
|
||||
deferred.promise()
|
||||
|
||||
EditableView
|
||||
@@ -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);
|
||||
|
||||
@@ -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: =>
|
||||
|
||||
8
static/js/dss/apps/activity/models/a_activityItem.coffee
Normal file
8
static/js/dss/apps/activity/models/a_activityItem.coffee
Normal 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
|
||||
|
||||
19
static/js/dss/apps/activity/models/activityCollection.coffee
Normal file
19
static/js/dss/apps/activity/models/activityCollection.coffee
Normal 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
|
||||
|
||||
@@ -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
|
||||
21
static/js/dss/apps/activity/views/activityListView.coffee
Normal file
21
static/js/dss/apps/activity/views/activityListView.coffee
Normal 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
|
||||
@@ -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")
|
||||
|
||||
185
static/js/dss/apps/mix/views/mixEditView.coffee
Normal file
185
static/js/dss/apps/mix/views/mixEditView.coffee
Normal 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')
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
@Dss.module "NotificationApp.Views", (Views, App, Backbone, Marionette) ->
|
||||
class Views.NotificationsItemView extends Marionette.CompositeView
|
||||
template: "notificationsitemview"
|
||||
tagName: "li"
|
||||
@@ -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
|
||||
47
static/js/dss/apps/nowplaying/views/nowPlayingView.coffee
Normal file
47
static/js/dss/apps/nowplaying/views/nowPlayingView.coffee
Normal 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
|
||||
4
static/js/dss/apps/search/views/searchItemView.coffee
Normal file
4
static/js/dss/apps/search/views/searchItemView.coffee
Normal file
@@ -0,0 +1,4 @@
|
||||
@Dss.module "SearchApp.Views", (Views, App, Backbone, Marionette, $, _, vent) ->
|
||||
class Views.SearchItemView extends Marionette.ItemView
|
||||
template: "searchresultview"
|
||||
el: "li"
|
||||
@@ -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
|
||||
52
static/js/dss/apps/sidebar/views/sidebarView.coffee
Normal file
52
static/js/dss/apps/sidebar/views/sidebarView.coffee
Normal 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -14,3 +14,6 @@ _.addTemplateHelpers
|
||||
|
||||
secondsToHms: (d) ->
|
||||
utils.secondsToHms d
|
||||
|
||||
canHomepage: () ->
|
||||
return com.podnoms.settings.canHomepage
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
44
static/js/dss/lib/social.coffee
Normal file
44
static/js/dss/lib/social.coffee
Normal 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") + "&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"
|
||||
|
||||
@@ -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) ->
|
||||
25
static/js/dss/templates/activityitemview.jst
Normal file
25
static/js/dss/templates/activityitemview.jst
Normal 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>
|
||||
11
static/js/dss/templates/activitylistview.jst
Normal file
11
static/js/dss/templates/activitylistview.jst
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
149
static/js/dss/templates/mixeditview.jst
Normal file
149
static/js/dss/templates/mixeditview.jst
Normal 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>
|
||||
@@ -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>
|
||||
<% } %>
|
||||
|
||||
13
static/js/dss/templates/notificationsitemview.jst
Normal file
13
static/js/dss/templates/notificationsitemview.jst
Normal 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>
|
||||
11
static/js/dss/templates/notificationslistview.jst
Normal file
11
static/js/dss/templates/notificationslistview.jst
Normal 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>
|
||||
30
static/js/dss/templates/nowplaying.jst
Normal file
30
static/js/dss/templates/nowplaying.jst
Normal 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>
|
||||
@@ -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
|
||||
|
||||
16
static/js/dss/templates/searchresultview.jst
Normal file
16
static/js/dss/templates/searchresultview.jst
Normal 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>
|
||||
4
static/js/dss/templates/sidebarview.jst
Normal file
4
static/js/dss/templates/sidebarview.jst
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
}
|
||||
|
||||
this.$stepContainer = $(this.$element.data('target') || 'body');//ACE
|
||||
console.log(this.$stepContainer);
|
||||
};
|
||||
|
||||
Wizard.prototype = {
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
198
static/js/lib/jasny.fileinput.js
Normal file
198
static/js/lib/jasny.fileinput.js
Normal 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);
|
||||
@@ -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()});
|
||||
|
||||
|
||||
@@ -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' %}
|
||||
|
||||
|
||||
@@ -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 }}",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user