Initial commit after change to backbone/SPA
1
core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
26
core/decorators.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from decorator import decorator
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template.context import RequestContext
|
||||
|
||||
@decorator
|
||||
def render_template(func, *args, **kwargs):
|
||||
"""
|
||||
using example:
|
||||
@render_template
|
||||
def view(request, template='abc.html'):
|
||||
slot = "this is a slot"
|
||||
return template, {'slot' : slot}
|
||||
"""
|
||||
request = args[0]
|
||||
_call = func(*args, **kwargs)
|
||||
|
||||
if isinstance(_call, HttpResponseRedirect):
|
||||
return _call
|
||||
|
||||
if isinstance(_call, tuple):
|
||||
template, context = _call
|
||||
else:
|
||||
template, context = _call, {}
|
||||
|
||||
return render_to_response(template, context_instance=RequestContext(request, context))
|
||||
1
core/tasks/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
8
core/tasks/waveform.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from celery.task import task
|
||||
from core.utils.waveform import generate_waveform
|
||||
|
||||
@task(name='dss.create_waveform_task')
|
||||
def create_waveform_task(in_file, out_file):
|
||||
generate_waveform(in_file, out_file)
|
||||
|
||||
|
||||
1
core/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
5
core/utils/file.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import uuid
|
||||
|
||||
__author__ = 'fergalm'
|
||||
def generate_save_file_name(prefix, filename):
|
||||
return '/'.join([prefix, str(uuid.uuid1()), filename])
|
||||
18
core/utils/html.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from HTMLParser import HTMLParser
|
||||
|
||||
class HTMLStripper(HTMLParser):
|
||||
"""
|
||||
Class that cleans HTML, removing all tags and HTML entities.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
self.fed = []
|
||||
def handle_data(self, d):
|
||||
self.fed.append(d)
|
||||
def get_data(self):
|
||||
return ''.join(self.fed)
|
||||
def strip(self, d):
|
||||
self.reset()
|
||||
self.fed = []
|
||||
self.feed(d)
|
||||
return self.get_data().strip()
|
||||
35
core/utils/live.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import urllib2
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
def get_server_details(server, port, mount):
|
||||
server = "http://%s:%s/status.xsl?mount=/%s" % (server, port, mount)
|
||||
print "Getting info for %s" % (server)
|
||||
try:
|
||||
response = urllib2.urlopen(server)
|
||||
html = response.read()
|
||||
if html:
|
||||
soup = BeautifulSoup(html)
|
||||
|
||||
info = {}
|
||||
info['stream_title'] = soup.find(text="Stream Title:").findNext('td').contents[0]
|
||||
info['stream_description'] = soup.find(text="Stream Description:").findNext('td').contents[0]
|
||||
info['content_type'] = soup.find(text="Content Type:").findNext('td').contents[0]
|
||||
info['mount_started'] = soup.find(text="Mount started:").findNext('td').contents[0]
|
||||
info['quality'] = soup.find(text="Quality:").findNext('td').contents[0]
|
||||
info['current_listeners'] = soup.find(text="Current Listeners:").findNext('td').contents[0]
|
||||
info['peak_listeners'] = soup.find(text="Peak Listeners:").findNext('td').contents[0]
|
||||
info['stream_genre'] = soup.find(text="Stream Genre:").findNext('td').contents[0]
|
||||
info['stream_url'] = soup.find(text="Stream URL:").findNext('td').findNext('a').contents[0]
|
||||
info['current_song'] = soup.find(text="Current Song:").findNext('td').contents[0]
|
||||
|
||||
return info
|
||||
else:
|
||||
print "Invalid content found"
|
||||
return None
|
||||
|
||||
except urllib2.URLError:
|
||||
print "Unable to read url, please check your parameters"
|
||||
return None
|
||||
|
||||
def get_now_playing(server, port, mount):
|
||||
return get_server_details(server, port, mount)['current_song']
|
||||
36
core/utils/waveform.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import subprocess
|
||||
import traceback
|
||||
import uuid
|
||||
import os
|
||||
from dss import settings
|
||||
|
||||
__author__ = 'fergalm'
|
||||
|
||||
def generate_waveform(input_file, output_file):
|
||||
print "Generating waveform"
|
||||
try:
|
||||
working_file = "%s%s.wav" % (settings.DSS_TEMP_PATH, uuid.uuid1())
|
||||
try:
|
||||
print "Starting decode : %s\nInput File: %s\nOutput File: %s" % (settings.DSS_LAME_PATH, input_file, working_file)
|
||||
p = subprocess.call([settings.DSS_LAME_PATH, "--decode", input_file, working_file])
|
||||
print "Finished decode"
|
||||
if os.path.exists(working_file):
|
||||
print "Starting waveform generation"
|
||||
print "%s -m -l -i %s -o -b 000000 %s" % (settings.DSS_WAVE_PATH, working_file, output_file)
|
||||
subprocess.call([settings.DSS_WAVE_PATH, "-t", "-m", "-l", "-i", working_file, "-o", output_file])
|
||||
|
||||
if os.path.isfile(output_file):
|
||||
os.remove(working_file)
|
||||
print "Generated waveform"
|
||||
return output_file
|
||||
else:
|
||||
print "Failed generating waveform: %s" % output_file
|
||||
else:
|
||||
print "Unable to find working file, did LAME succeed?"
|
||||
return ""
|
||||
except Exception, ex:
|
||||
print "Error generating waveform %s" % (ex)
|
||||
|
||||
except:
|
||||
print "Error generating waveform"
|
||||
traceback.print_exc()
|
||||
0
dss/__init__.py
Normal file
161
dss/settings.py
Normal file
@@ -0,0 +1,161 @@
|
||||
# Django settings for dss project.
|
||||
from datetime import timedelta
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
import djcelery
|
||||
import os
|
||||
from utils import here
|
||||
from django.conf import global_settings
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
AUTH_PROFILE_MODULE = 'spa.UserProfile'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'deepsouthsounds',
|
||||
'USER': 'deepsouthsounds',
|
||||
'PASSWORD': '',
|
||||
'HOST': '',
|
||||
'PORT': '',
|
||||
}
|
||||
}
|
||||
ROOT_URLCONF = 'dss.urls'
|
||||
TIME_ZONE = 'America/Chicago'
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
SITE_ID = 1
|
||||
USE_I18N = False
|
||||
USE_L10N = True
|
||||
USE_TZ = False
|
||||
|
||||
SITE_ROOT = here('')
|
||||
MEDIA_ROOT = here('media')
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
STATIC_ROOT = ''
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
STATICFILES_DIRS = (
|
||||
here('static'),
|
||||
)
|
||||
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
)
|
||||
|
||||
SECRET_KEY = '8*&j)j4lnq*ft*=jhajvc7&upaifb2f2s5(v6i($$+3p(4^bvd'
|
||||
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
'django.template.loaders.eggs.Loader',
|
||||
#'django.template.loaders.app_directories.load_template_source',
|
||||
)
|
||||
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
|
||||
'django.core.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
"allauth.socialaccount.context_processors.socialaccount",
|
||||
"allauth.account.context_processors.account"
|
||||
)
|
||||
AUTHENTICATION_BACKENDS = global_settings.AUTHENTICATION_BACKENDS + (
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
WSGI_APPLICATION = 'dss.wsgi.application'
|
||||
TEMPLATE_DIRS = (here('templates'),)
|
||||
INSTALLED_APPS = (
|
||||
'grappelli',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.admindocs',
|
||||
'djcelery',
|
||||
'avatar',
|
||||
'notification',
|
||||
'spa',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'allauth.socialaccount.providers.facebook',
|
||||
'allauth.socialaccount.providers.google',
|
||||
'allauth.socialaccount.providers.github',
|
||||
'allauth.socialaccount.providers.linkedin',
|
||||
'allauth.socialaccount.providers.openid',
|
||||
'allauth.socialaccount.providers.twitter',
|
||||
'emailconfirmation',
|
||||
'backbone_tastypie',
|
||||
)
|
||||
|
||||
# where to redirect users to after logging in
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('home')
|
||||
LOGOUT_URL = reverse_lazy('home')
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
BROKER_HOST = "127.0.0.1"
|
||||
BROKER_PORT = 5672
|
||||
BROKER_VHOST = "/"
|
||||
BROKER_USER = "guest"
|
||||
BROKER_PASSWORD = "guest"
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
"runs-every-30-seconds": {
|
||||
"task": "dss.generate_missing_waveforms_task",
|
||||
"schedule": timedelta(seconds=30),
|
||||
},
|
||||
}
|
||||
djcelery.setup_loader()
|
||||
|
||||
SOCIALACCOUNT_AVATAR_SUPPORT = True
|
||||
AVATAR_STORAGE_DIR = MEDIA_ROOT + 'avatars/'
|
||||
|
||||
if os.name == 'posix':
|
||||
DSS_TEMP_PATH = "/tmp/"
|
||||
DSS_LAME_PATH = "/usr/bin/lame"
|
||||
DSS_WAVE_PATH = "/usr/local/bin/waveformgen"
|
||||
else:
|
||||
DSS_TEMP_PATH = "d:\\temp\\"
|
||||
DSS_LAME_PATH = "D:\\Apps\\lame\\lame.exe"
|
||||
DSS_WAVE_PATH = "d:\\Apps\\waveformgen.exe"
|
||||
30
dss/urls.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
from dss import settings
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
# from django.contrib import admin
|
||||
# admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/img/favicon.ico'}),
|
||||
url(r'^', include('spa.urls')),
|
||||
(r'^grappelli/', include('grappelli.urls')),
|
||||
url(r'^accounts/', include('allauth.urls')),
|
||||
(r'^avatar/', include('avatar.urls')),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.views.static import serve
|
||||
|
||||
_media_url = settings.MEDIA_URL
|
||||
if _media_url.startswith('/'):
|
||||
_media_url = _media_url[1:]
|
||||
urlpatterns += patterns('',
|
||||
(r'^%s(?P<path>.*)$' % _media_url,
|
||||
serve,
|
||||
{'document_root': settings.MEDIA_ROOT}))
|
||||
del(_media_url, serve)
|
||||
28
dss/wsgi.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
WSGI config for dss project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
and any production WSGI deployments. It should expose a module-level variable
|
||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||
this application via the ``WSGI_APPLICATION`` setting.
|
||||
|
||||
Usually you will have the standard Django WSGI application here, but it also
|
||||
might make sense to replace the whole Django WSGI application with a custom one
|
||||
that later delegates to the Django one. For example, you could introduce WSGI
|
||||
middleware here, or combine a Django application with an application of another
|
||||
framework.
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dss.settings")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
# from helloworld.wsgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
||||
10
manage.py
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dss.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
15
requirements.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
Django>=1.4
|
||||
MySQL-python>=1.2.3
|
||||
pillow>=1.7.7
|
||||
beautifulsoup4>=4.1.1
|
||||
decorator>=3.3.3
|
||||
django-allauth>=0.7.0
|
||||
django-annoying>=0.7.6
|
||||
django-celery>=3.0.4
|
||||
django-gravatar2>=1.0.6
|
||||
django-avatar>=1.0.5
|
||||
django-notification>=0.2
|
||||
django-socialauth>=0.1.2c
|
||||
django-tastypie>=0.9.11
|
||||
django-grappelli
|
||||
humanize
|
||||
0
spa/__init__.py
Normal file
16
spa/admin.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.contrib import admin
|
||||
from spa.models import Mix, Label, Release, ReleaseAudio, MixLike
|
||||
|
||||
class DefaultAdmin(admin.ModelAdmin):
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.user = request.user.get_profile()
|
||||
obj.save()
|
||||
|
||||
#admin.site.register(Event, EventTestPageAdmin)
|
||||
admin.site.register(Mix)
|
||||
admin.site.register(MixLike)
|
||||
#admin.site.register(Venue)
|
||||
#admin.site.register(Genre)
|
||||
admin.site.register(Label)
|
||||
admin.site.register(Release, DefaultAdmin)
|
||||
admin.site.register(ReleaseAudio)
|
||||
111
spa/ajax.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponse
|
||||
from annoying.decorators import render_to
|
||||
from django.shortcuts import render_to_response
|
||||
import json
|
||||
from django.utils import simplejson
|
||||
from core.utils import live
|
||||
from spa.models import Mix, Comment, MixLike
|
||||
|
||||
class AjaxHandler(object):
|
||||
import logging
|
||||
|
||||
# Get an instance of a logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self, api_name="v1"):
|
||||
self.api_name = api_name
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
pattern_list = [
|
||||
url(r'^mix-description/(?P<mix_id>\d+)/$', 'spa.ajax.get_mix_description', name='ajax_mix-description'),
|
||||
url(r'^mix/add_comment/$', 'spa.ajax.mix_add_comment', name='mix_add_comment'),
|
||||
url(r'^mix/comments/(?P<mix_id>\d+)/$', 'spa.ajax.mix_comments', name='ajax_mix_comments'),
|
||||
url(r'^header/$', 'spa.ajax.header', name='header'),
|
||||
url(r'^mix_stream_url/(?P<mix_id>\d+)/$', 'spa.ajax.get_mix_stream_url'),
|
||||
url(r'^release_player/(?P<release_id>\d+)/$', 'spa.ajax.release_player'),
|
||||
url(r'^live_now_playing/$', 'spa.ajax.live_now_playing'),
|
||||
url(r'^like/$', 'spa.ajax.like', name='ajax_mix-description'),
|
||||
]
|
||||
return pattern_list
|
||||
|
||||
def wrap_view(self, view):
|
||||
def wrapper(request, *args, **kwargs):
|
||||
return getattr(self, view)(request, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _get_json(payload, key='value'):
|
||||
data = {
|
||||
key: payload
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
def get_mix_description(request, mix_id):
|
||||
return HttpResponse(json.dumps(_get_json('ArgleBargle')), mimetype="application/json")
|
||||
|
||||
|
||||
@render_to('inc/header.html')
|
||||
def header(request):
|
||||
return HttpResponse(render_to_response('inc/header.html'))
|
||||
|
||||
|
||||
def get_mix_stream_url(request, mix_id):
|
||||
try:
|
||||
mix = Mix.objects.get(pk=mix_id)
|
||||
mix.add_play(request.user)
|
||||
data = {
|
||||
'stream_url': mix.get_stream_path(),
|
||||
'description': mix.description,
|
||||
'title': mix.title
|
||||
}
|
||||
return HttpResponse(json.dumps(data), mimetype="application/json")
|
||||
except Exception, e:
|
||||
self.logger.exception("Error getting mix stream url")
|
||||
|
||||
def live_now_playing(request):
|
||||
return HttpResponse(
|
||||
json.dumps({
|
||||
'stream_url': "radio.deepsouthsounds.com",
|
||||
'description': 'Description',
|
||||
'title': live.get_now_playing("radio.deepsouthsounds.com", "8000", "live")
|
||||
}), mimetype="application/json")
|
||||
|
||||
|
||||
@render_to('inc/release_player.html')
|
||||
def release_player(request, release_id):
|
||||
return HttpResponse('Hello Sailor')
|
||||
|
||||
def mix_add_comment(request):
|
||||
if request.POST:
|
||||
comment = Comment()
|
||||
comment.mix_id = request.POST['mixid']
|
||||
comment.user = request.user
|
||||
comment.comment = request.POST['comment']
|
||||
comment.time_index = request.POST['position']
|
||||
comment.save()
|
||||
|
||||
return HttpResponse(simplejson.dumps({'description': 'Hello Sailor'}))
|
||||
else:
|
||||
return HttpResponse(simplejson.dumps({'description': 'Error posting'}))
|
||||
|
||||
@render_to('inc/comment_list.html')
|
||||
def mix_comments(request, mix_id):
|
||||
return {
|
||||
"results": Comment.objects.filter(mix_id=mix_id),
|
||||
}
|
||||
|
||||
@login_required()
|
||||
def like(request):
|
||||
if request.is_ajax():
|
||||
if request.method == 'POST':
|
||||
if request.POST['dataMode'] == 'mix':
|
||||
mix = Mix.objects.get(pk = request.POST['dataId'])
|
||||
if mix is not None:
|
||||
mix.likes.add(MixLike(mix = mix, user = request.user))
|
||||
mix.save()
|
||||
return HttpResponse(simplejson.dumps(request.raw_post_data))
|
||||
1
spa/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
21
spa/api/v1/BackboneCompatibleResource.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.conf.urls import url
|
||||
from tastypie import fields
|
||||
from tastypie.resources import ModelResource
|
||||
|
||||
__author__ = 'fergalm'
|
||||
|
||||
class BackboneCompatibleResource(ModelResource):
|
||||
|
||||
def override_urls(self):
|
||||
urls = []
|
||||
|
||||
for name, field in self.fields.items():
|
||||
if isinstance(field, fields.ToManyField):
|
||||
resource = r"^(?P<resource_name>{resource_name})/(?P<{related_name}>.+)/{related_resource}/$".format(
|
||||
resource_name=self._meta.resource_name,
|
||||
related_name=field.related_name,
|
||||
related_resource=field.attribute,
|
||||
)
|
||||
resource = url(resource, field.to_class().wrap_view('get_list'), name="api_dispatch_detail")
|
||||
urls.append(resource)
|
||||
return urls
|
||||
36
spa/api/v1/CommentResource.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import datetime
|
||||
import humanize
|
||||
from tastypie import fields
|
||||
from tastypie.authentication import Authentication
|
||||
from tastypie.authorization import Authorization
|
||||
from spa.api.v1.BackboneCompatibleResource import BackboneCompatibleResource
|
||||
from spa.models import Comment
|
||||
|
||||
class CommentResource(BackboneCompatibleResource):
|
||||
mix = fields.ToOneField('spa.api.v1.MixResource.MixResource', 'mix')
|
||||
|
||||
class Meta:
|
||||
queryset = Comment.objects.all().order_by('-date_created')
|
||||
resource_name = 'comments'
|
||||
filtering = {
|
||||
"mix": ('exact',),
|
||||
}
|
||||
authorization = Authorization()
|
||||
authentication = Authentication()
|
||||
always_return_data = True
|
||||
|
||||
def obj_create(self, bundle, request, **kwargs):
|
||||
bundle.data['user'] = {'pk': request.user.pk}
|
||||
return super(CommentResource, self).obj_create(bundle, request, user=request.user)
|
||||
|
||||
def dehydrate_date_created(self, bundle):
|
||||
if (datetime.datetime.now() - bundle.obj.date_created) <= datetime.timedelta(days=1):
|
||||
return humanize.naturaltime(bundle.obj.date_created)
|
||||
else:
|
||||
return humanize.naturalday(bundle.obj.date_created)
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
bundle.data['avatar_image'] = bundle.obj.user.get_profile().get_avatar_image(150)
|
||||
bundle.data['user_url'] = bundle.obj.user.get_absolute_url()
|
||||
bundle.data['user_name'] = bundle.obj.user.get_profile().nice_name()
|
||||
return bundle
|
||||
43
spa/api/v1/MixResource.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from django.db.models.aggregates import Count
|
||||
from tastypie import fields
|
||||
from tastypie.authorization import Authorization
|
||||
from tastypie.constants import ALL_WITH_RELATIONS
|
||||
from spa.api.v1.BackboneCompatibleResource import BackboneCompatibleResource
|
||||
|
||||
from spa.models import Mix
|
||||
|
||||
class MixResource(BackboneCompatibleResource):
|
||||
comments = fields.ToManyField('spa.api.v1.CommentResource.CommentResource', 'comments', 'mix')
|
||||
|
||||
class Meta:
|
||||
queryset = Mix.objects.filter(is_active=True)
|
||||
excludes = ['download_url', 'is_active', 'local_file', 'upload_date']
|
||||
filtering = {
|
||||
'comments' : ALL_WITH_RELATIONS
|
||||
}
|
||||
authorization = Authorization()
|
||||
|
||||
def obj_get_list(self, request=None, **kwargs):
|
||||
sort = 'latest'
|
||||
if 'sort' in request.GET and request.GET['sort']:
|
||||
sort = request.GET['sort']
|
||||
|
||||
return Mix.get_listing(sort)
|
||||
|
||||
def dehydrate_mix_image(self, bundle):
|
||||
return bundle.obj.get_image()
|
||||
|
||||
def dehydrate_description(self, bundle):
|
||||
return bundle.obj.description.replace("\n", "<br />")
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
bundle.data['waveform_url'] = bundle.obj.get_waveform_url()
|
||||
bundle.data['user_name'] = bundle.obj.user.nice_name()
|
||||
bundle.data['item_url'] = 'mix/%s' % bundle.obj.id
|
||||
|
||||
bundle.data['play_count'] = bundle.obj.plays.count()
|
||||
bundle.data['like_count'] = bundle.obj.likes.count()
|
||||
bundle.data['mode'] = 'mix'
|
||||
bundle.data['comment_count'] = bundle.obj.comments.count()
|
||||
return bundle
|
||||
|
||||
17
spa/api/v1/ReleaseAudioResource.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from tastypie import fields
|
||||
from spa.api.v1.BackboneCompatibleResource import BackboneCompatibleResource
|
||||
from spa.models import ReleaseAudio
|
||||
|
||||
class ReleaseAudioResource(BackboneCompatibleResource):
|
||||
release = fields.ToOneField('spa.api.v1.ReleaseResource.ReleaseResource', 'release')
|
||||
|
||||
class Meta:
|
||||
queryset = ReleaseAudio.objects.all()
|
||||
resource_name = 'audio'
|
||||
filtering = {
|
||||
"release": ('exact',),
|
||||
}
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
bundle.data['waveform_url'] = bundle.obj.get_waveform_url()
|
||||
return bundle
|
||||
24
spa/api/v1/ReleaseResource.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import datetime
|
||||
import humanize
|
||||
from tastypie import fields
|
||||
from tastypie.authorization import Authorization
|
||||
from tastypie.constants import ALL_WITH_RELATIONS
|
||||
from spa.api.v1.BackboneCompatibleResource import BackboneCompatibleResource
|
||||
from spa.models import Release
|
||||
|
||||
class ReleaseResource(BackboneCompatibleResource):
|
||||
release_audio = fields.ToManyField('spa.api.v1.ReleaseAudioResource.ReleaseAudioResource', 'release_audio', 'release')
|
||||
class Meta:
|
||||
queryset = Release.objects.all()
|
||||
filtering = {
|
||||
'release_audio' : ALL_WITH_RELATIONS
|
||||
}
|
||||
authorization = Authorization()
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
bundle.data['item_url'] = 'release/%s' % bundle.obj.id
|
||||
bundle.data['mode'] = 'release'
|
||||
return bundle
|
||||
|
||||
def dehydrate_release_date(self, bundle):
|
||||
return humanize.naturalday(bundle.obj.release_date)
|
||||
1
spa/api/v1/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
1
spa/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
1
spa/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
21
spa/management/commands/drop.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.conf import settings
|
||||
|
||||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = "Drop and re-create the database"
|
||||
def handle_noargs(self, **options):
|
||||
import MySQLdb
|
||||
|
||||
print "Connecting..."
|
||||
db = MySQLdb.connect(
|
||||
host=settings.DATABASES['default']['HOST'] or "localhost",
|
||||
user=settings.DATABASES['default']['USER'],
|
||||
passwd=settings.DATABASES['default']['PASSWORD'],
|
||||
port=int(settings.DATABASES['default']['PORT'] or 3306))
|
||||
|
||||
cursor = db.cursor()
|
||||
print "Dropping database %s" % settings.DATABASES['default']['NAME']
|
||||
cursor.execute("drop database %s; create database %s;" % (settings.DATABASES['default']['NAME'], settings.DATABASES['default']['NAME']))
|
||||
print "Dropped"
|
||||
|
||||
29
spa/management/commands/generate_waveforms.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import os
|
||||
from dss import settings
|
||||
from core.utils.waveform import generate_waveform
|
||||
from spa.models import Mix, ReleaseAudio
|
||||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
|
||||
help = "Generate all outstanding waveforms"
|
||||
|
||||
def _check_file(self, local_file, output_file):
|
||||
if os.path.isfile(os.path.join(settings.MEDIA_ROOT, local_file)):
|
||||
file = os.path.join(settings.MEDIA_ROOT, output_file)
|
||||
if not os.path.isfile(file):
|
||||
print "Found missing waveform"
|
||||
generate_waveform(local_file, file)
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
print "Generating waveforms for mix"
|
||||
objects = Mix.objects.all()
|
||||
for object in objects:
|
||||
output_file = 'waveforms/mix/%d.png' % object.pk
|
||||
self._check_file(object.local_file.file.name, output_file)
|
||||
|
||||
print "Generating waveforms for release"
|
||||
objects = ReleaseAudio.objects.all()
|
||||
for object in objects:
|
||||
output_file = 'waveforms/release/%d.png' % object.pk
|
||||
self._check_file(object.local_file.file.name, output_file)
|
||||
261
spa/models.py
Normal file
@@ -0,0 +1,261 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import urlparse
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.db.models.aggregates import Count
|
||||
from django.db.models.signals import post_save
|
||||
from django_gravatar.helpers import has_gravatar, get_gravatar_url
|
||||
import os
|
||||
from core.utils.file import generate_save_file_name
|
||||
from dss import settings
|
||||
from tasks.waveform import create_waveform_task
|
||||
|
||||
def mix_file_name(instance, filename):
|
||||
return generate_save_file_name('mixes', filename)
|
||||
|
||||
|
||||
def mix_image_name(instance, filename):
|
||||
return generate_save_file_name('mix-images', filename)
|
||||
|
||||
|
||||
def venue_image_name(instance, filename):
|
||||
return generate_save_file_name('venue-images', filename)
|
||||
|
||||
|
||||
def release_image_name(instance, filename):
|
||||
return generate_save_file_name('release-images', filename)
|
||||
|
||||
|
||||
def release_file_name(instance, filename):
|
||||
return generate_save_file_name('release-audio', filename)
|
||||
|
||||
class BaseModel(models.Model):
|
||||
logger = logging.getLogger(__name__)
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class UserProfile(BaseModel):
|
||||
class Meta:
|
||||
db_table = 'www_userprofile'
|
||||
|
||||
# This field is required.
|
||||
user = models.ForeignKey(User, unique=True)
|
||||
avatar_type = models.CharField(max_length=15)
|
||||
avatar_image = models.ImageField(blank=True, upload_to='avatars/')
|
||||
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
UserProfile.objects.create(user=instance)
|
||||
post_save.connect(create_user_profile, sender=User)
|
||||
|
||||
def get_username(self):
|
||||
return self.user.username
|
||||
username = property(get_username)
|
||||
|
||||
def get_email(self):
|
||||
return self.user.email
|
||||
email = property(get_email)
|
||||
|
||||
def get_first_name(self):
|
||||
return self.user.first_name
|
||||
first_name = property(get_first_name)
|
||||
|
||||
def get_last_name(self):
|
||||
return self.user.last_name
|
||||
last_name = property(get_last_name)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('user_details', kwargs={'user_name': self.user.username})
|
||||
|
||||
def nice_name(self):
|
||||
return self.first_name + ' ' + self.last_name
|
||||
|
||||
def get_avatar_image(self, size=150):
|
||||
avatar_type = self.avatar_type
|
||||
if avatar_type == 'gravatar':
|
||||
gravatar_exists = has_gravatar(self.email)
|
||||
if gravatar_exists:
|
||||
return get_gravatar_url(self.email, size)
|
||||
elif avatar_type == 'social':
|
||||
try:
|
||||
social_account = SocialAccount.objects.filter(user = self)[0]
|
||||
if social_account:
|
||||
provider = social_account.get_provider_account()
|
||||
return provider.get_avatar_url()
|
||||
except:
|
||||
pass
|
||||
elif avatar_type == 'custom':
|
||||
return self.avatar_image.url
|
||||
|
||||
return urlparse.urljoin(settings.STATIC_URL, "img/default-avatar-32.png")
|
||||
|
||||
class Mix(BaseModel):
|
||||
class Meta:
|
||||
db_table = 'www_mix'
|
||||
|
||||
title = models.CharField(max_length=50)
|
||||
description = models.TextField()
|
||||
upload_date = models.DateTimeField(default=datetime.now())
|
||||
mix_image = models.ImageField(blank=True, upload_to=mix_image_name)
|
||||
local_file = models.FileField(upload_to=mix_file_name)
|
||||
download_url = models.CharField(max_length=255)
|
||||
stream_url = models.CharField(max_length=255)
|
||||
is_active = models.BooleanField(default=True)
|
||||
user = models.ForeignKey(UserProfile, editable=False)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None):
|
||||
super(Mix, self).save(force_insert, force_update, using)
|
||||
if not os.path.exists(self.get_waveform_path()):
|
||||
create_waveform_task.delay(id=self.id, in_file=self.local_file.file.name)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/mix/%i' % self.id
|
||||
|
||||
def get_waveform_path(self):
|
||||
return os.path.join(settings.MEDIA_ROOT, "waveforms/mix/", "%d.%s" % (self.id, "png"))
|
||||
|
||||
def get_waveform_url(self):
|
||||
return settings.MEDIA_URL + 'waveforms/mix/%d.%s' % (self.id, "png")
|
||||
|
||||
def get_image(self):
|
||||
try:
|
||||
if self.mix_image:
|
||||
if os.path.exists(self.mix_image.file.name):
|
||||
return self.mix_image.url
|
||||
except Exception as ex:
|
||||
pass
|
||||
|
||||
return settings.STATIC_URL + 'img/default-track.png'
|
||||
|
||||
def get_stream_path(self):
|
||||
return settings.MEDIA_URL + self.local_file.name
|
||||
|
||||
@classmethod
|
||||
def get_listing(cls, listing_type):
|
||||
queryset = None
|
||||
if listing_type == 'latest':
|
||||
queryset = Mix.objects.all().order_by( 'id')
|
||||
elif listing_type == 'toprated':
|
||||
queryset = Mix.objects.all()\
|
||||
.annotate(karma=Count('likes'))\
|
||||
.order_by('-karma')
|
||||
elif listing_type == 'mostactive':
|
||||
queryset = Mix.objects.all()\
|
||||
.annotate(karma=Count('comments'))\
|
||||
.order_by('-karma')
|
||||
elif listing_type == 'mostplayed':
|
||||
queryset = Mix.objects.all()\
|
||||
.annotate(karma=Count('likes'))\
|
||||
.order_by('-karma')
|
||||
elif listing_type == 'recommended':
|
||||
queryset = Mix.objects.all().order_by( '-id')
|
||||
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def get_user_mixes(cls, user_name):
|
||||
mixes = Mix.objects.filter(user__user__username=user_name)
|
||||
if mixes.count():
|
||||
return {
|
||||
"inline_play": False,
|
||||
"heading": "Some mixes from " + mixes[0].user.user.get_full_name() or mixes[0].user.user.username,
|
||||
"latest_mix_list": mixes,
|
||||
}
|
||||
|
||||
return {
|
||||
"heading": "No mixes found for this user",
|
||||
"latest_mix_list": None,
|
||||
}
|
||||
|
||||
def add_play(self, user):
|
||||
try:
|
||||
self.plays.add(MixPlay(user = user if user.is_authenticated() else None))
|
||||
except Exception, e:
|
||||
self.logger.exception("Error getting mix stream url")
|
||||
|
||||
class Comment(BaseModel):
|
||||
class Meta:
|
||||
db_table = 'www_comment'
|
||||
|
||||
user = models.ForeignKey(User, editable=False)
|
||||
mix = models.ForeignKey(Mix, editable=False, related_name='comments')
|
||||
comment = models.CharField(max_length=1024)
|
||||
date_created = models.DateTimeField(auto_now=True)
|
||||
time_index = models.IntegerField()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/comment/%i' % self.id
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None):
|
||||
super(Comment, self).save(force_insert, force_update, using)
|
||||
|
||||
class Label(BaseModel):
|
||||
class Meta:
|
||||
db_table = 'www_label'
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Release(BaseModel):
|
||||
class Meta:
|
||||
db_table = 'www_release'
|
||||
|
||||
release_artist = models.CharField(max_length=100)
|
||||
release_title = models.CharField(max_length=100)
|
||||
release_description = models.TextField()
|
||||
release_image = models.ImageField(blank=True, upload_to=release_image_name)
|
||||
release_label = models.ForeignKey(Label)
|
||||
release_date = models.DateField(default=datetime.now())
|
||||
|
||||
is_active = models.BooleanField(default=True)
|
||||
user = models.ForeignKey(UserProfile, editable=False)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.release_title
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None):
|
||||
super(Release, self).save(force_insert, force_update, using)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/release/%i' % self.id
|
||||
|
||||
@classmethod
|
||||
def get_view_model(cls):
|
||||
qs = cls.objects.get(is_active=True)
|
||||
return qs
|
||||
|
||||
class ReleaseAudio(BaseModel):
|
||||
class Meta:
|
||||
db_table = 'www_releaseaudio'
|
||||
|
||||
def __unicode__(self):
|
||||
return self.description
|
||||
|
||||
def get_waveform_url(self):
|
||||
return settings.MEDIA_URL + 'waveforms/release/%d.%s' % (self.id, "png")
|
||||
|
||||
local_file = models.FileField(upload_to=release_file_name)
|
||||
release = models.ForeignKey(Release, related_name='release_audio')
|
||||
description = models.TextField()
|
||||
|
||||
class __Like(BaseModel):
|
||||
date = models.DateTimeField(auto_now=True)
|
||||
user = models.ForeignKey(User, null=True )
|
||||
|
||||
class MixLike(__Like):
|
||||
class Meta:
|
||||
db_table = 'www_like'
|
||||
mix = models.ForeignKey(Mix, related_name='likes')
|
||||
|
||||
class MixPlay(__Like):
|
||||
class Meta:
|
||||
db_table = 'www_play'
|
||||
mix = models.ForeignKey(Mix, related_name='plays')
|
||||
9
spa/templates.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template.context import RequestContext
|
||||
|
||||
__author__ = 'fergalm'
|
||||
|
||||
def get_template(request, template_name):
|
||||
return render_to_response(
|
||||
'views/%s.html' % (template_name),
|
||||
context_instance=RequestContext(request))
|
||||
1
spa/templatetags/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'fergalm'
|
||||
40
spa/templatetags/spa_extras.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from django import template
|
||||
from django.contrib.auth.models import User
|
||||
from django_gravatar.helpers import has_gravatar, get_gravatar_url
|
||||
from dss import settings
|
||||
from spa.models import UserProfile
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def nice_name(user):
|
||||
if user == "":
|
||||
return "Unknown User"
|
||||
|
||||
if type(user) is UserProfile:
|
||||
return user.user.get_full_name() or user.user.username
|
||||
elif type(user) is User:
|
||||
return user.get_full_name() or user.username
|
||||
|
||||
@register.filter
|
||||
def avatar_image(user, size=150):
|
||||
profile = user.get_profile()
|
||||
avatar_type = profile.avatar_type
|
||||
|
||||
if avatar_type == 'gravatar':
|
||||
gravatar_exists = has_gravatar(user.email)
|
||||
if gravatar_exists:
|
||||
return get_gravatar_url(user.email, size)
|
||||
elif avatar_type == 'social':
|
||||
try:
|
||||
social_account = SocialAccount.objects.filter(user = user)[0]
|
||||
if social_account:
|
||||
provider = social_account.get_provider_account()
|
||||
return provider.get_avatar_url()
|
||||
except:
|
||||
pass
|
||||
elif avatar_type == 'custom':
|
||||
return profile.avatar_image.url
|
||||
|
||||
return settings.STATIC_URL + "/images/default-avatar-32.png"
|
||||
16
spa/tests.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
This file demonstrates writing tests using the unittest module. These will pass
|
||||
when you run "manage.py test".
|
||||
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
||||
25
spa/urls.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from django.conf.urls import patterns, url, include
|
||||
import django.conf.urls
|
||||
from tastypie.api import Api
|
||||
from spa.ajax import AjaxHandler
|
||||
from spa.api.v1.CommentResource import CommentResource
|
||||
from spa.api.v1.MixResource import MixResource
|
||||
from spa.api.v1.ReleaseAudioResource import ReleaseAudioResource
|
||||
from spa.api.v1.ReleaseResource import ReleaseResource
|
||||
import spa
|
||||
|
||||
v1_api = Api(api_name='v1')
|
||||
v1_api.register(MixResource())
|
||||
v1_api.register(CommentResource())
|
||||
v1_api.register(ReleaseResource())
|
||||
v1_api.register(ReleaseAudioResource())
|
||||
|
||||
ajax = AjaxHandler()
|
||||
|
||||
urlpatterns = django.conf.urls.patterns(
|
||||
'',
|
||||
url(r'^$', 'spa.views.app', name='home'),
|
||||
url(r'^tpl/(?P<template_name>\w+)/$', 'spa.templates.get_template'),
|
||||
(r'^ajax/', include(ajax.urls)),
|
||||
(r'^api/', include(v1_api.urls)),
|
||||
)
|
||||
5
spa/views.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from core.decorators import render_template
|
||||
|
||||
@render_template
|
||||
def app(request):
|
||||
return "inc/app.html"
|
||||
BIN
static/bin/sm/soundmanager2.swf
Normal file
BIN
static/bin/sm/soundmanager2_debug.swf
Normal file
BIN
static/bin/sm/soundmanager2_flash9.swf
Normal file
BIN
static/bin/sm/soundmanager2_flash9_debug.swf
Normal file
BIN
static/bin/sm/soundmanager2_flash_xdomain.zip
Normal file
808
static/css/bootstrap-responsive.css
vendored
Normal file
@@ -0,0 +1,808 @@
|
||||
/*!
|
||||
* Bootstrap Responsive v2.0.3
|
||||
*
|
||||
* Copyright 2012 Twitter, Inc
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Designed and built with all the love in the world @twitter by @mdo and @fat.
|
||||
*/
|
||||
|
||||
.clearfix {
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.clearfix:before,
|
||||
.clearfix:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.hide-text {
|
||||
font: 0/0 a;
|
||||
color: transparent;
|
||||
text-shadow: none;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.input-block-level {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.visible-phone {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.visible-tablet {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hidden-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.visible-phone {
|
||||
display: inherit !important;
|
||||
}
|
||||
.hidden-phone {
|
||||
display: none !important;
|
||||
}
|
||||
.hidden-desktop {
|
||||
display: inherit !important;
|
||||
}
|
||||
.visible-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
.visible-tablet {
|
||||
display: inherit !important;
|
||||
}
|
||||
.hidden-tablet {
|
||||
display: none !important;
|
||||
}
|
||||
.hidden-desktop {
|
||||
display: inherit !important;
|
||||
}
|
||||
.visible-desktop {
|
||||
display: none !important ;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.nav-collapse {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.page-header h1 small {
|
||||
display: block;
|
||||
line-height: 18px;
|
||||
}
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.form-horizontal .control-group > label {
|
||||
float: none;
|
||||
width: auto;
|
||||
padding-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
.form-horizontal .controls {
|
||||
margin-left: 0;
|
||||
}
|
||||
.form-horizontal .control-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
.form-horizontal .form-actions {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.modal {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
.modal.fade.in {
|
||||
top: auto;
|
||||
}
|
||||
.modal-header .close {
|
||||
padding: 10px;
|
||||
margin: -10px;
|
||||
}
|
||||
.carousel-caption {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.navbar-fixed-top,
|
||||
.navbar-fixed-bottom {
|
||||
margin-right: -20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
.container-fluid {
|
||||
padding: 0;
|
||||
}
|
||||
.dl-horizontal dt {
|
||||
float: none;
|
||||
width: auto;
|
||||
clear: none;
|
||||
text-align: left;
|
||||
}
|
||||
.dl-horizontal dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
.container {
|
||||
width: auto;
|
||||
}
|
||||
.row-fluid {
|
||||
width: 100%;
|
||||
}
|
||||
.row,
|
||||
.thumbnails {
|
||||
margin-left: 0;
|
||||
}
|
||||
[class*="span"],
|
||||
.row-fluid [class*="span"] {
|
||||
display: block;
|
||||
float: none;
|
||||
width: auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
.input-large,
|
||||
.input-xlarge,
|
||||
.input-xxlarge,
|
||||
input[class*="span"],
|
||||
select[class*="span"],
|
||||
textarea[class*="span"],
|
||||
.uneditable-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.input-prepend input,
|
||||
.input-append input,
|
||||
.input-prepend input[class*="span"],
|
||||
.input-append input[class*="span"] {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
.row {
|
||||
margin-left: -20px;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row:before,
|
||||
.row:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row:after {
|
||||
clear: both;
|
||||
}
|
||||
[class*="span"] {
|
||||
float: left;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.container,
|
||||
.navbar-fixed-top .container,
|
||||
.navbar-fixed-bottom .container {
|
||||
width: 724px;
|
||||
}
|
||||
.span12 {
|
||||
width: 724px;
|
||||
}
|
||||
.span11 {
|
||||
width: 662px;
|
||||
}
|
||||
.span10 {
|
||||
width: 600px;
|
||||
}
|
||||
.span9 {
|
||||
width: 538px;
|
||||
}
|
||||
.span8 {
|
||||
width: 476px;
|
||||
}
|
||||
.span7 {
|
||||
width: 414px;
|
||||
}
|
||||
.span6 {
|
||||
width: 352px;
|
||||
}
|
||||
.span5 {
|
||||
width: 290px;
|
||||
}
|
||||
.span4 {
|
||||
width: 228px;
|
||||
}
|
||||
.span3 {
|
||||
width: 166px;
|
||||
}
|
||||
.span2 {
|
||||
width: 104px;
|
||||
}
|
||||
.span1 {
|
||||
width: 42px;
|
||||
}
|
||||
.offset12 {
|
||||
margin-left: 764px;
|
||||
}
|
||||
.offset11 {
|
||||
margin-left: 702px;
|
||||
}
|
||||
.offset10 {
|
||||
margin-left: 640px;
|
||||
}
|
||||
.offset9 {
|
||||
margin-left: 578px;
|
||||
}
|
||||
.offset8 {
|
||||
margin-left: 516px;
|
||||
}
|
||||
.offset7 {
|
||||
margin-left: 454px;
|
||||
}
|
||||
.offset6 {
|
||||
margin-left: 392px;
|
||||
}
|
||||
.offset5 {
|
||||
margin-left: 330px;
|
||||
}
|
||||
.offset4 {
|
||||
margin-left: 268px;
|
||||
}
|
||||
.offset3 {
|
||||
margin-left: 206px;
|
||||
}
|
||||
.offset2 {
|
||||
margin-left: 144px;
|
||||
}
|
||||
.offset1 {
|
||||
margin-left: 82px;
|
||||
}
|
||||
.row-fluid {
|
||||
width: 100%;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row-fluid:before,
|
||||
.row-fluid:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row-fluid:after {
|
||||
clear: both;
|
||||
}
|
||||
.row-fluid [class*="span"] {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
margin-left: 2.762430939%;
|
||||
*margin-left: 2.709239449638298%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.row-fluid [class*="span"]:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.row-fluid .span12 {
|
||||
width: 99.999999993%;
|
||||
*width: 99.9468085036383%;
|
||||
}
|
||||
.row-fluid .span11 {
|
||||
width: 91.436464082%;
|
||||
*width: 91.38327259263829%;
|
||||
}
|
||||
.row-fluid .span10 {
|
||||
width: 82.87292817100001%;
|
||||
*width: 82.8197366816383%;
|
||||
}
|
||||
.row-fluid .span9 {
|
||||
width: 74.30939226%;
|
||||
*width: 74.25620077063829%;
|
||||
}
|
||||
.row-fluid .span8 {
|
||||
width: 65.74585634900001%;
|
||||
*width: 65.6926648596383%;
|
||||
}
|
||||
.row-fluid .span7 {
|
||||
width: 57.182320438000005%;
|
||||
*width: 57.129128948638304%;
|
||||
}
|
||||
.row-fluid .span6 {
|
||||
width: 48.618784527%;
|
||||
*width: 48.5655930376383%;
|
||||
}
|
||||
.row-fluid .span5 {
|
||||
width: 40.055248616%;
|
||||
*width: 40.0020571266383%;
|
||||
}
|
||||
.row-fluid .span4 {
|
||||
width: 31.491712705%;
|
||||
*width: 31.4385212156383%;
|
||||
}
|
||||
.row-fluid .span3 {
|
||||
width: 22.928176794%;
|
||||
*width: 22.874985304638297%;
|
||||
}
|
||||
.row-fluid .span2 {
|
||||
width: 14.364640883%;
|
||||
*width: 14.311449393638298%;
|
||||
}
|
||||
.row-fluid .span1 {
|
||||
width: 5.801104972%;
|
||||
*width: 5.747913482638298%;
|
||||
}
|
||||
input,
|
||||
textarea,
|
||||
.uneditable-input {
|
||||
margin-left: 0;
|
||||
}
|
||||
input.span12,
|
||||
textarea.span12,
|
||||
.uneditable-input.span12 {
|
||||
width: 714px;
|
||||
}
|
||||
input.span11,
|
||||
textarea.span11,
|
||||
.uneditable-input.span11 {
|
||||
width: 652px;
|
||||
}
|
||||
input.span10,
|
||||
textarea.span10,
|
||||
.uneditable-input.span10 {
|
||||
width: 590px;
|
||||
}
|
||||
input.span9,
|
||||
textarea.span9,
|
||||
.uneditable-input.span9 {
|
||||
width: 528px;
|
||||
}
|
||||
input.span8,
|
||||
textarea.span8,
|
||||
.uneditable-input.span8 {
|
||||
width: 466px;
|
||||
}
|
||||
input.span7,
|
||||
textarea.span7,
|
||||
.uneditable-input.span7 {
|
||||
width: 404px;
|
||||
}
|
||||
input.span6,
|
||||
textarea.span6,
|
||||
.uneditable-input.span6 {
|
||||
width: 342px;
|
||||
}
|
||||
input.span5,
|
||||
textarea.span5,
|
||||
.uneditable-input.span5 {
|
||||
width: 280px;
|
||||
}
|
||||
input.span4,
|
||||
textarea.span4,
|
||||
.uneditable-input.span4 {
|
||||
width: 218px;
|
||||
}
|
||||
input.span3,
|
||||
textarea.span3,
|
||||
.uneditable-input.span3 {
|
||||
width: 156px;
|
||||
}
|
||||
input.span2,
|
||||
textarea.span2,
|
||||
.uneditable-input.span2 {
|
||||
width: 94px;
|
||||
}
|
||||
input.span1,
|
||||
textarea.span1,
|
||||
.uneditable-input.span1 {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.row {
|
||||
margin-left: -30px;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row:before,
|
||||
.row:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row:after {
|
||||
clear: both;
|
||||
}
|
||||
[class*="span"] {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
}
|
||||
.container,
|
||||
.navbar-fixed-top .container,
|
||||
.navbar-fixed-bottom .container {
|
||||
width: 1170px;
|
||||
}
|
||||
.span12 {
|
||||
width: 1170px;
|
||||
}
|
||||
.span11 {
|
||||
width: 1070px;
|
||||
}
|
||||
.span10 {
|
||||
width: 970px;
|
||||
}
|
||||
.span9 {
|
||||
width: 870px;
|
||||
}
|
||||
.span8 {
|
||||
width: 770px;
|
||||
}
|
||||
.span7 {
|
||||
width: 670px;
|
||||
}
|
||||
.span6 {
|
||||
width: 570px;
|
||||
}
|
||||
.span5 {
|
||||
width: 470px;
|
||||
}
|
||||
.span4 {
|
||||
width: 370px;
|
||||
}
|
||||
.span3 {
|
||||
width: 270px;
|
||||
}
|
||||
.span2 {
|
||||
width: 170px;
|
||||
}
|
||||
.span1 {
|
||||
width: 70px;
|
||||
}
|
||||
.offset12 {
|
||||
margin-left: 1230px;
|
||||
}
|
||||
.offset11 {
|
||||
margin-left: 1130px;
|
||||
}
|
||||
.offset10 {
|
||||
margin-left: 1030px;
|
||||
}
|
||||
.offset9 {
|
||||
margin-left: 930px;
|
||||
}
|
||||
.offset8 {
|
||||
margin-left: 830px;
|
||||
}
|
||||
.offset7 {
|
||||
margin-left: 730px;
|
||||
}
|
||||
.offset6 {
|
||||
margin-left: 630px;
|
||||
}
|
||||
.offset5 {
|
||||
margin-left: 530px;
|
||||
}
|
||||
.offset4 {
|
||||
margin-left: 430px;
|
||||
}
|
||||
.offset3 {
|
||||
margin-left: 330px;
|
||||
}
|
||||
.offset2 {
|
||||
margin-left: 230px;
|
||||
}
|
||||
.offset1 {
|
||||
margin-left: 130px;
|
||||
}
|
||||
.row-fluid {
|
||||
width: 100%;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row-fluid:before,
|
||||
.row-fluid:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row-fluid:after {
|
||||
clear: both;
|
||||
}
|
||||
.row-fluid [class*="span"] {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
margin-left: 2.564102564%;
|
||||
*margin-left: 2.510911074638298%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.row-fluid [class*="span"]:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.row-fluid .span12 {
|
||||
width: 100%;
|
||||
*width: 99.94680851063829%;
|
||||
}
|
||||
.row-fluid .span11 {
|
||||
width: 91.45299145300001%;
|
||||
*width: 91.3997999636383%;
|
||||
}
|
||||
.row-fluid .span10 {
|
||||
width: 82.905982906%;
|
||||
*width: 82.8527914166383%;
|
||||
}
|
||||
.row-fluid .span9 {
|
||||
width: 74.358974359%;
|
||||
*width: 74.30578286963829%;
|
||||
}
|
||||
.row-fluid .span8 {
|
||||
width: 65.81196581200001%;
|
||||
*width: 65.7587743226383%;
|
||||
}
|
||||
.row-fluid .span7 {
|
||||
width: 57.264957265%;
|
||||
*width: 57.2117657756383%;
|
||||
}
|
||||
.row-fluid .span6 {
|
||||
width: 48.717948718%;
|
||||
*width: 48.6647572286383%;
|
||||
}
|
||||
.row-fluid .span5 {
|
||||
width: 40.170940171000005%;
|
||||
*width: 40.117748681638304%;
|
||||
}
|
||||
.row-fluid .span4 {
|
||||
width: 31.623931624%;
|
||||
*width: 31.5707401346383%;
|
||||
}
|
||||
.row-fluid .span3 {
|
||||
width: 23.076923077%;
|
||||
*width: 23.0237315876383%;
|
||||
}
|
||||
.row-fluid .span2 {
|
||||
width: 14.529914530000001%;
|
||||
*width: 14.4767230406383%;
|
||||
}
|
||||
.row-fluid .span1 {
|
||||
width: 5.982905983%;
|
||||
*width: 5.929714493638298%;
|
||||
}
|
||||
input,
|
||||
textarea,
|
||||
.uneditable-input {
|
||||
margin-left: 0;
|
||||
}
|
||||
input.span12,
|
||||
textarea.span12,
|
||||
.uneditable-input.span12 {
|
||||
width: 1160px;
|
||||
}
|
||||
input.span11,
|
||||
textarea.span11,
|
||||
.uneditable-input.span11 {
|
||||
width: 1060px;
|
||||
}
|
||||
input.span10,
|
||||
textarea.span10,
|
||||
.uneditable-input.span10 {
|
||||
width: 960px;
|
||||
}
|
||||
input.span9,
|
||||
textarea.span9,
|
||||
.uneditable-input.span9 {
|
||||
width: 860px;
|
||||
}
|
||||
input.span8,
|
||||
textarea.span8,
|
||||
.uneditable-input.span8 {
|
||||
width: 760px;
|
||||
}
|
||||
input.span7,
|
||||
textarea.span7,
|
||||
.uneditable-input.span7 {
|
||||
width: 660px;
|
||||
}
|
||||
input.span6,
|
||||
textarea.span6,
|
||||
.uneditable-input.span6 {
|
||||
width: 560px;
|
||||
}
|
||||
input.span5,
|
||||
textarea.span5,
|
||||
.uneditable-input.span5 {
|
||||
width: 460px;
|
||||
}
|
||||
input.span4,
|
||||
textarea.span4,
|
||||
.uneditable-input.span4 {
|
||||
width: 360px;
|
||||
}
|
||||
input.span3,
|
||||
textarea.span3,
|
||||
.uneditable-input.span3 {
|
||||
width: 260px;
|
||||
}
|
||||
input.span2,
|
||||
textarea.span2,
|
||||
.uneditable-input.span2 {
|
||||
width: 160px;
|
||||
}
|
||||
input.span1,
|
||||
textarea.span1,
|
||||
.uneditable-input.span1 {
|
||||
width: 60px;
|
||||
}
|
||||
.thumbnails {
|
||||
margin-left: -30px;
|
||||
}
|
||||
.thumbnails > li {
|
||||
margin-left: 30px;
|
||||
}
|
||||
.row-fluid .thumbnails {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 979px) {
|
||||
body {
|
||||
padding-top: 0;
|
||||
}
|
||||
.navbar-fixed-top {
|
||||
position: static;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.navbar-fixed-top .navbar-inner {
|
||||
padding: 5px;
|
||||
}
|
||||
.navbar .container {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
.navbar .brand {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
margin: 0 0 0 -5px;
|
||||
}
|
||||
.nav-collapse {
|
||||
clear: both;
|
||||
}
|
||||
.nav-collapse .nav {
|
||||
float: none;
|
||||
margin: 0 0 9px;
|
||||
}
|
||||
.nav-collapse .nav > li {
|
||||
float: none;
|
||||
}
|
||||
.nav-collapse .nav > li > a {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.nav-collapse .nav > .divider-vertical {
|
||||
display: none;
|
||||
}
|
||||
.nav-collapse .nav .nav-header {
|
||||
color: #999999;
|
||||
text-shadow: none;
|
||||
}
|
||||
.nav-collapse .nav > li > a,
|
||||
.nav-collapse .dropdown-menu a {
|
||||
padding: 6px 15px;
|
||||
font-weight: bold;
|
||||
color: #999999;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.nav-collapse .btn {
|
||||
padding: 4px 10px 4px;
|
||||
font-weight: normal;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.nav-collapse .dropdown-menu li + li a {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.nav-collapse .nav > li > a:hover,
|
||||
.nav-collapse .dropdown-menu a:hover {
|
||||
background-color: #222222;
|
||||
}
|
||||
.nav-collapse.in .btn-group {
|
||||
padding: 0;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.nav-collapse .dropdown-menu {
|
||||
position: static;
|
||||
top: auto;
|
||||
left: auto;
|
||||
display: block;
|
||||
float: none;
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
margin: 0 15px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.nav-collapse .dropdown-menu:before,
|
||||
.nav-collapse .dropdown-menu:after {
|
||||
display: none;
|
||||
}
|
||||
.nav-collapse .dropdown-menu .divider {
|
||||
display: none;
|
||||
}
|
||||
.nav-collapse .navbar-form,
|
||||
.nav-collapse .navbar-search {
|
||||
float: none;
|
||||
padding: 9px 15px;
|
||||
margin: 9px 0;
|
||||
border-top: 1px solid #222222;
|
||||
border-bottom: 1px solid #222222;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.navbar .nav-collapse .nav.pull-right {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
.nav-collapse,
|
||||
.nav-collapse.collapse {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.navbar .btn-navbar {
|
||||
display: block;
|
||||
}
|
||||
.navbar-static .navbar-inner {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 980px) {
|
||||
.nav-collapse.collapse {
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
4326
static/css/bootstrap.css
vendored
Normal file
52
static/css/colorbox.css
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
ColorBox Core Style:
|
||||
The following CSS is consistent between example themes and should not be altered.
|
||||
*/
|
||||
#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
|
||||
#cboxOverlay{position:fixed; width:100%; height:100%;}
|
||||
#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
|
||||
#cboxContent{position:relative;}
|
||||
#cboxLoadedContent{overflow:auto;}
|
||||
#cboxTitle{margin:0;}
|
||||
#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;}
|
||||
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
|
||||
.cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:none;}
|
||||
.cboxIframe{width:100%; height:100%; display:block; border:0;}
|
||||
#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box;}
|
||||
|
||||
/*
|
||||
User Style:
|
||||
Change the following styles to modify the appearance of ColorBox. They are
|
||||
ordered & tabbed in a way that represents the nesting of the generated HTML.
|
||||
*/
|
||||
#cboxOverlay{background:#000;}
|
||||
#colorbox{}
|
||||
#cboxTopLeft{width:14px; height:14px; background:url(../img/colorbox/controls.png) no-repeat 0 0;}
|
||||
#cboxTopCenter{height:14px; background:url(../img/colorbox/border.png) repeat-x top left;}
|
||||
#cboxTopRight{width:14px; height:14px; background:url(../img/colorbox/controls.png) no-repeat -36px 0;}
|
||||
#cboxBottomLeft{width:14px; height:43px; background:url(../img/colorbox/controls.png) no-repeat 0 -32px;}
|
||||
#cboxBottomCenter{height:43px; background:url(../img/colorbox/border.png) repeat-x bottom left;}
|
||||
#cboxBottomRight{width:14px; height:43px; background:url(../img/colorbox/controls.png) no-repeat -36px -32px;}
|
||||
#cboxMiddleLeft{width:14px; background:url(../img/colorbox/controls.png) repeat-y -175px 0;}
|
||||
#cboxMiddleRight{width:14px; background:url(../img/colorbox/controls.png) repeat-y -211px 0;}
|
||||
#cboxContent{background:#fff; overflow:visible;}
|
||||
.cboxIframe{background:#fff;}
|
||||
#cboxError{padding:50px; border:1px solid #ccc;}
|
||||
#cboxLoadedContent{margin-bottom:5px;}
|
||||
#cboxLoadingOverlay{background:url(../img/colorbox/loading_background.png) no-repeat center center;}
|
||||
#cboxLoadingGraphic{background:url(../img/colorbox/loading.gif) no-repeat center center;}
|
||||
#cboxTitle{position:absolute; bottom:-25px; left:0; text-align:center; width:100%; font-weight:bold; color:#7C7C7C;}
|
||||
#cboxCurrent{position:absolute; bottom:-25px; left:58px; font-weight:bold; color:#7C7C7C;}
|
||||
|
||||
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{position:absolute; bottom:-29px; background:url(../img/colorbox/controls.png) no-repeat 0px 0px; width:23px; height:23px; text-indent:-9999px;}
|
||||
#cboxPrevious{left:0px; background-position: -51px -25px;}
|
||||
#cboxPrevious:hover{background-position:-51px 0px;}
|
||||
#cboxNext{left:27px; background-position:-75px -25px;}
|
||||
#cboxNext:hover{background-position:-75px 0px;}
|
||||
#cboxClose{right:0; background-position:-100px -25px;}
|
||||
#cboxClose:hover{background-position:-100px 0px;}
|
||||
|
||||
.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px; right:27px;}
|
||||
.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px;}
|
||||
.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px; right:27px;}
|
||||
.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px;}
|
||||
167
static/css/fmplayer.css
Normal file
@@ -0,0 +1,167 @@
|
||||
.player-wrapper {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.player-header {
|
||||
border: 1px #CCC solid;
|
||||
border-bottom: 0;
|
||||
padding: 0px 4px 0px 4px;
|
||||
background: #C7BF9E;
|
||||
position: relative;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
-moz-border-radius-topleft: 4px;
|
||||
border-top-left-radius: 4px;
|
||||
-webkit-border-top-left-radius: 4px;
|
||||
-moz-border-radius-topright: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
-webkit-border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.player-seekhead {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background: none repeat scroll 0 0 #EB0C7A;
|
||||
height: 92px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.player-body {
|
||||
height: 104px;
|
||||
position: relative;
|
||||
border: 1px #CCC solid;
|
||||
background-color: #EEE;
|
||||
-webkit-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
div.player-body ul.player-controls a {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
.player-title a {
|
||||
text-decoration: none;
|
||||
color: #3A1E20;
|
||||
}
|
||||
|
||||
.player-title li {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.player-title li {
|
||||
font-size: 10px;
|
||||
line-height: 21px;
|
||||
color: #C7BF9E;
|
||||
}
|
||||
|
||||
.cp_container {
|
||||
border-right: 1px #CCC solid;
|
||||
|
||||
}
|
||||
|
||||
.player-progress {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.bottom-wrapper {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.player-bottom {
|
||||
border-top: 1px #CCC solid;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
color: #423B35;
|
||||
background-color: #283031;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
top: 90px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.waveform img {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.download-progress-overlay {
|
||||
|
||||
background-image: url('../img/download-progress-overlay.png');
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
height: 81px;
|
||||
display: block;
|
||||
background-position: top right;
|
||||
-webkit-box-shadow: 0px 0px 4px rgba(255, 0, 140, 0.2);
|
||||
}
|
||||
|
||||
.playhead {
|
||||
background-image: url('../img/playhead.png');
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0px;
|
||||
height: 81px;
|
||||
display: block;
|
||||
background-position: top right;
|
||||
-webkit-box-shadow: 0px 0px 4px rgba(255, 0, 140, 0.2);
|
||||
}
|
||||
|
||||
/* Listing CSS */
|
||||
.audio-listing {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mix-image-image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/*
|
||||
width: 220px;
|
||||
height: 120px;
|
||||
*/
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.audio-listing-item {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.player-footer {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
padding-top: 12px;
|
||||
border-bottom: 1px solid #F4F4F4;
|
||||
}
|
||||
|
||||
.play-button-small {
|
||||
display: block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.play-button-small-playing {
|
||||
background: url(../img/play.png) no-repeat;
|
||||
}
|
||||
|
||||
.play-button-small-paused {
|
||||
background: url(../img/pause.png) no-repeat;
|
||||
}
|
||||
|
||||
.footer-button {
|
||||
float: left;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
.footer-button-right {
|
||||
float: right;
|
||||
}
|
||||
155
static/css/style.css
Normal file
@@ -0,0 +1,155 @@
|
||||
body {
|
||||
/* background: url(../images/pattern1.jpg) repeat #ffffff;*/
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
padding-top: 45px;
|
||||
padding-bottom: 40px;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
#header {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border-bottom: 3px solid #CEC3B3;
|
||||
}
|
||||
|
||||
#mix-comments-list ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.comment-listing .comment-entry {
|
||||
border-bottom: 1px solid #D2D9E7;
|
||||
}
|
||||
|
||||
.comment-entry {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.comment-details {
|
||||
padding-top: 1px;
|
||||
padding-left: 5px;
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
width: 10000px;
|
||||
}
|
||||
|
||||
.image-avatar-small {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.image-avatar-small {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.socialaccount_provider {
|
||||
display: block;
|
||||
height: 116px;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.signin_button {
|
||||
display: block;
|
||||
height: 22px;
|
||||
width: 150px;
|
||||
font-size: .1em;
|
||||
}
|
||||
|
||||
.social_login_providers {
|
||||
display: inline;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.social_login_providers li {
|
||||
padding: 7px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#twitter_button {
|
||||
background: transparent url(../img/signin_twitter.png) top left no-repeat;
|
||||
}
|
||||
|
||||
#facebook_button {
|
||||
background: transparent url(../img/signin_facebook.png) top left no-repeat;
|
||||
}
|
||||
|
||||
#twitter_button:hover, #facebook_button:hover {
|
||||
background-position: 0px -24px;
|
||||
}
|
||||
|
||||
#twitter_button:active, #facebook_button:active {
|
||||
background-position: 0px -48px;
|
||||
}
|
||||
|
||||
.release-image {
|
||||
max-width: 100%;
|
||||
height: 200px;
|
||||
width: 200px; /* ie8 */
|
||||
}
|
||||
|
||||
#release-audio-slide-nav li {
|
||||
display: inline;
|
||||
list-style-type: none;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.selector-button a.on {
|
||||
background-position: 0 -24px;
|
||||
}
|
||||
|
||||
.selector-button a {
|
||||
background-image: url(../img/slide-nav.png);
|
||||
|
||||
float: left;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline;
|
||||
font-size: 11px;
|
||||
margin: 0 5px 0 0;
|
||||
line-height: 24px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.selector-button a:link, .selector-button a:visited {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.list-nostyle {
|
||||
list-style: none outside none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.list-horiz li {
|
||||
float: left;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bordered-right {
|
||||
border-right: 1px solid #F2F2F2;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
border-right-color: #E5E5E5;
|
||||
margin-right: 7px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
.stats{
|
||||
border: 0;
|
||||
font: 0/0 a;
|
||||
text-shadow: none;
|
||||
color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
0
static/css/sup.css
Normal file
BIN
static/img/colorbox/border.png
Normal file
|
After Width: | Height: | Size: 163 B |
BIN
static/img/colorbox/controls.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
static/img/colorbox/loading.gif
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
static/img/colorbox/loading_background.png
Normal file
|
After Width: | Height: | Size: 166 B |
BIN
static/img/default-avatar-32.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
static/img/default-avatar.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
static/img/default-track.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
static/img/download-progress-overlay.png
Normal file
|
After Width: | Height: | Size: 173 B |
BIN
static/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
static/img/glyphicons-halflings-white.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
static/img/glyphicons-halflings.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
static/img/info.png
Normal file
|
After Width: | Height: | Size: 189 B |
BIN
static/img/noise.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/img/pause.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
static/img/play.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/playhead.png
Normal file
|
After Width: | Height: | Size: 170 B |
BIN
static/img/sheen3.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
static/img/signin_facebook.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
static/img/signin_twitter.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
static/img/site-logo-gr.png
Normal file
|
After Width: | Height: | Size: 859 B |
BIN
static/img/slide-nav.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
206
static/img/variables.less
Normal file
@@ -0,0 +1,206 @@
|
||||
// Variables.less
|
||||
// Variables to customize the look and feel of Bootstrap
|
||||
// Swatch: United
|
||||
// Version: 2.0.4
|
||||
// -----------------------------------------------------
|
||||
|
||||
// GLOBAL VALUES
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
// Grays
|
||||
// -------------------------
|
||||
@black: #000;
|
||||
@grayDarker: #222;
|
||||
@grayDark: #333;
|
||||
@gray: #555;
|
||||
@grayLight: #999;
|
||||
@grayLighter: #F5F5F5;
|
||||
@white: #fff;
|
||||
|
||||
|
||||
// Accent colors
|
||||
// -------------------------
|
||||
@blue: #19B6EE;
|
||||
@blueDark: #0064cd;
|
||||
@green: #38B44A;
|
||||
@red: #DF382C;
|
||||
@yellow: #EFB73E;
|
||||
@orange: #DD4814;
|
||||
@pink: #c3325f;
|
||||
@purple: #772953;
|
||||
|
||||
|
||||
// Scaffolding
|
||||
// -------------------------
|
||||
@bodyBackground: @white;
|
||||
@textColor: @grayDark;
|
||||
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
@linkColor: @orange;
|
||||
@linkColorHover: darken(@linkColor, 15%);
|
||||
|
||||
|
||||
// Typography
|
||||
// -------------------------
|
||||
@sansFontFamily: 'Ubuntu', Tahoma, sans-serif;
|
||||
@serifFontFamily: Georgia, "Times New Roman", Times, serif;
|
||||
@monoFontFamily: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
|
||||
@baseFontSize: 13px;
|
||||
@baseFontFamily: @sansFontFamily;
|
||||
@baseLineHeight: 18px;
|
||||
@altFontFamily: @serifFontFamily;
|
||||
|
||||
@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily
|
||||
@headingsFontWeight: bold; // instead of browser default, bold
|
||||
@headingsColor: inherit; // empty to use BS default, @textColor
|
||||
|
||||
|
||||
// Tables
|
||||
// -------------------------
|
||||
@tableBackground: transparent; // overall background-color
|
||||
@tableBackgroundAccent: #f9f9f9; // for striping
|
||||
@tableBackgroundHover: #f5f5f5; // for hover
|
||||
@tableBorder: #ddd; // table and cell border
|
||||
|
||||
|
||||
// Buttons
|
||||
// -------------------------
|
||||
@btnBackground: @white;
|
||||
@btnBackgroundHighlight: darken(@white, 10%);
|
||||
@btnBorder: darken(@white, 20%);
|
||||
|
||||
@btnPrimaryBackground: @linkColor;
|
||||
@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 15%);
|
||||
|
||||
@btnInfoBackground: lighten(@purple, 15%);
|
||||
@btnInfoBackgroundHighlight: @purple;
|
||||
|
||||
@btnSuccessBackground: #62c462;
|
||||
@btnSuccessBackgroundHighlight: #51a351;
|
||||
|
||||
@btnWarningBackground: lighten(@orange, 15%);
|
||||
@btnWarningBackgroundHighlight: @orange;
|
||||
|
||||
@btnDangerBackground: #ee5f5b;
|
||||
@btnDangerBackgroundHighlight: #bd362f;
|
||||
|
||||
@btnInverseBackground: @gray;
|
||||
@btnInverseBackgroundHighlight: @grayDarker;
|
||||
|
||||
|
||||
// Forms
|
||||
// -------------------------
|
||||
@inputBackground: @white;
|
||||
@inputBorder: #ccc;
|
||||
@inputBorderRadius: 3px;
|
||||
@inputDisabledBackground: @grayLighter;
|
||||
@formActionsBackground: transparent;
|
||||
|
||||
// Dropdowns
|
||||
// -------------------------
|
||||
@dropdownBackground: @white;
|
||||
@dropdownBorder: rgba(0,0,0,.2);
|
||||
@dropdownLinkColor: @linkColor;
|
||||
@dropdownLinkColorHover: @white;
|
||||
@dropdownLinkBackgroundHover: @linkColor;
|
||||
@dropdownDividerTop: #e5e5e5;
|
||||
@dropdownDividerBottom: @white;
|
||||
|
||||
|
||||
|
||||
// COMPONENT VARIABLES
|
||||
// --------------------------------------------------
|
||||
|
||||
// Z-index master list
|
||||
// -------------------------
|
||||
// Used for a bird's eye view of components dependent on the z-axis
|
||||
// Try to avoid customizing these :)
|
||||
@zindexDropdown: 1000;
|
||||
@zindexPopover: 1010;
|
||||
@zindexTooltip: 1020;
|
||||
@zindexFixedNavbar: 1030;
|
||||
@zindexModalBackdrop: 1040;
|
||||
@zindexModal: 1050;
|
||||
|
||||
|
||||
// Sprite icons path
|
||||
// -------------------------
|
||||
@iconSpritePath: "../img/glyphicons-halflings.png";
|
||||
@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png";
|
||||
|
||||
|
||||
// Input placeholder text color
|
||||
// -------------------------
|
||||
@placeholderText: @grayLight;
|
||||
|
||||
|
||||
// Hr border color
|
||||
// -------------------------
|
||||
@hrBorder: @grayLighter;
|
||||
|
||||
|
||||
// Navbar
|
||||
// -------------------------
|
||||
@navbarHeight: 40px;
|
||||
@navbarBackground: @orange;
|
||||
@navbarBackgroundHighlight: #CE4213;
|
||||
|
||||
@navbarText: @white;
|
||||
@navbarLinkColor: @white;
|
||||
@navbarLinkColorHover: @white;
|
||||
@navbarLinkColorActive: @navbarLinkColorHover;
|
||||
@navbarLinkBackgroundHover: transparent;
|
||||
@navbarLinkBackgroundActive: @navbarBackground;
|
||||
|
||||
@navbarSearchBackground: lighten(@navbarBackground, 25%);
|
||||
@navbarSearchBackgroundFocus: @white;
|
||||
@navbarSearchBorder: darken(@navbarSearchBackground, 30%);
|
||||
@navbarSearchPlaceholderColor: @white;
|
||||
@navbarBrandColor: @navbarLinkColor;
|
||||
|
||||
|
||||
// Hero unit
|
||||
// -------------------------
|
||||
@heroUnitBackground: @grayLighter;
|
||||
@heroUnitHeadingColor: inherit;
|
||||
@heroUnitLeadColor: inherit;
|
||||
|
||||
|
||||
// Form states and alerts
|
||||
// -------------------------
|
||||
@warningText: #ECA918;
|
||||
@warningBackground: lighten(@warningText, 40%);
|
||||
@warningBorder: darken(spin(@warningBackground, -10), 3%);
|
||||
|
||||
@errorText: #DF382C;
|
||||
@errorBackground: lighten(@errorText, 40%);
|
||||
@errorBorder: darken(spin(@errorBackground, -10), 3%);
|
||||
|
||||
@successText: #38B44A;
|
||||
@successBackground: lighten(@successText, 40%);
|
||||
@successBorder: darken(spin(@successBackground, -10), 5%);
|
||||
|
||||
@infoText: @purple;
|
||||
@infoBackground: lighten(@purple, 50%);
|
||||
@infoBorder: darken(spin(@infoBackground, -10), 7%);
|
||||
|
||||
|
||||
|
||||
// GRID
|
||||
// --------------------------------------------------
|
||||
|
||||
// Default 940px grid
|
||||
// -------------------------
|
||||
@gridColumns: 12;
|
||||
@gridColumnWidth: 60px;
|
||||
@gridGutterWidth: 20px;
|
||||
@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));
|
||||
|
||||
// Fluid grid
|
||||
// -------------------------
|
||||
@fluidGridColumnWidth: 6.382978723%;
|
||||
@fluidGridGutterWidth: 2.127659574%;
|
||||
92
static/js/app/app.js
Normal file
@@ -0,0 +1,92 @@
|
||||
var AppRouter = Backbone.Router.extend({
|
||||
routes:{
|
||||
"application/":"defaultRoute",
|
||||
"/mixes":"mixList",
|
||||
"/mixes/:type":"mixList",
|
||||
"/mix/:id":"mixDetails",
|
||||
"/releases":"releaseList",
|
||||
"/release/:id":"releaseDetails",
|
||||
"/accounts/login/":"login",
|
||||
"/accounts/logout/":"logout"
|
||||
},
|
||||
initialize:function () {
|
||||
this.headerView = new HeaderView();
|
||||
$('#header').html(this.headerView.el);
|
||||
this.sidebarView = new SidebarView();
|
||||
//$('#sidebar').html(this.sidebarView.el);
|
||||
$('#site-content-fill').html('');
|
||||
},
|
||||
defaultRoute:function (path) {
|
||||
if (path == "" || path == "/")
|
||||
this.mixList(0);
|
||||
},
|
||||
mixList:function (type) {
|
||||
var mixList = new MixCollection();
|
||||
mixList.type = type || 'latest';
|
||||
$('#site-content-fill').html('');
|
||||
var data = type != undefined ? $.param({sort: type}) : null;
|
||||
mixList.fetch({
|
||||
data: data,
|
||||
success:function () {
|
||||
var content = new MixListView({collection:mixList}).el;
|
||||
$('#content').html(content);
|
||||
}
|
||||
});
|
||||
},
|
||||
mixDetails:function (id) {
|
||||
var mix = new Mix({id:id});
|
||||
mix.fetch({success:function () {
|
||||
var html = new MixView({model:mix});
|
||||
$('#content').html(html.el);
|
||||
$('#site-content-fill').html('');
|
||||
var comments = new CommentCollection();
|
||||
comments.url = window.appSettings.urlRoot + mix.attributes.item_url + "/comments/";
|
||||
comments.mix_id = id;
|
||||
comments.mix = mix.get("resource_uri");
|
||||
comments.fetch({success:function (data) {
|
||||
var content = new CommentListView({collection:comments}).render();
|
||||
$('#site-content-fill').html(content.el);
|
||||
}});
|
||||
}});
|
||||
},
|
||||
releaseList:function (page) {
|
||||
|
||||
},
|
||||
releaseDetails:function (id) {
|
||||
var release = new Release({id:id});
|
||||
$('#site-content-fill').html('');
|
||||
release.fetch({success:function () {
|
||||
var content = new ReleaseView({model:release}).el;
|
||||
$('#content').html(content);
|
||||
var audio = new ReleaseAudioCollection();
|
||||
audio.url = window.appSettings.urlRoot + release.attributes.item_url + "/release_audio/";
|
||||
audio.audio_id = id;
|
||||
audio.release = release.get("resource_uri");
|
||||
audio.fetch({success:function (data) {
|
||||
var content = new ReleaseAudioListView({collection:audio});
|
||||
$('#release-description').html(content.el);
|
||||
}});
|
||||
}});
|
||||
},
|
||||
login:function () {
|
||||
$.colorbox({
|
||||
href:"/tpl/LoginView/",
|
||||
onClosed:function () {
|
||||
app.navigate('/');
|
||||
}
|
||||
})
|
||||
},
|
||||
logout:function () {
|
||||
window.utils.showAlert("Success", "You are now logged out", "alert-success");
|
||||
}
|
||||
});
|
||||
|
||||
utils.loadTemplate([
|
||||
'HeaderView', 'SidebarView',
|
||||
'MixListView', 'MixItemView', 'MixView',
|
||||
'CommentListView', 'CommentItemView',
|
||||
'ReleaseListView', 'ReleaseItemView', 'ReleaseView', 'ReleaseAudioListView', 'ReleaseAudioItemView'], function () {
|
||||
window.app = new AppRouter();
|
||||
Backbone.history.start();
|
||||
});
|
||||
var _eventAggregator = _.extend({}, Backbone.Events);
|
||||
10
static/js/app/models/comment.js
Normal file
@@ -0,0 +1,10 @@
|
||||
var Comment = TastypieModel.extend({
|
||||
urlRoot:window.appSettings.urlRoot + "comments/"
|
||||
});
|
||||
|
||||
var CommentCollection = TastypieCollection.extend({
|
||||
model:Comment,
|
||||
comparator: function(comment){
|
||||
return -comment.get("id");
|
||||
}
|
||||
});
|
||||
7
static/js/app/models/mix.js
Normal file
@@ -0,0 +1,7 @@
|
||||
window.Mix = TastypieModel.extend({
|
||||
urlRoot:window.appSettings.urlRoot + "mix/"
|
||||
});
|
||||
window.MixCollection = TastypieCollection.extend({
|
||||
url:window.appSettings.urlRoot + "mix/",
|
||||
model:Mix
|
||||
});
|
||||
7
static/js/app/models/release.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var Release = TastypieModel.extend({
|
||||
urlRoot:window.appSettings.urlRoot + "release/"
|
||||
});
|
||||
var ReleaseCollection = TastypieCollection.extend({
|
||||
url:window.appSettings.urlRoot + "release/",
|
||||
model:Release
|
||||
});
|
||||
7
static/js/app/models/release_audio.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var ReleaseAudio = TastypieModel.extend({
|
||||
urlRoot:window.appSettings.urlRoot + "release_audio/"
|
||||
});
|
||||
|
||||
var ReleaseAudioCollection = TastypieCollection.extend({
|
||||
model:ReleaseAudio
|
||||
});
|
||||
4
static/js/app/settings.js
Normal file
@@ -0,0 +1,4 @@
|
||||
if (!window.appSettings){
|
||||
window.appSettings = {};
|
||||
appSettings.urlRoot = "/api/v1/";
|
||||
}
|
||||
38
static/js/app/site.js
Normal file
@@ -0,0 +1,38 @@
|
||||
$(document).ajaxSend(function (event, xhr, settings) {
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function sameOrigin(url) {
|
||||
// url could be relative or scheme relative or absolute
|
||||
var host = document.location.host; // host + port
|
||||
var protocol = document.location.protocol;
|
||||
var sr_origin = '//' + host;
|
||||
var origin = protocol + sr_origin;
|
||||
// Allow absolute or scheme relative URLs to same origin
|
||||
return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
|
||||
(url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
|
||||
// or any other URL that isn't scheme relative or absolute i.e relative.
|
||||
!(/^(\/\/|http:|https:).*/.test(url));
|
||||
}
|
||||
|
||||
function safeMethod(method) {
|
||||
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||
}
|
||||
|
||||
if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
|
||||
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
|
||||
}
|
||||
});
|
||||
90
static/js/app/utils.js
Normal file
@@ -0,0 +1,90 @@
|
||||
window.utils = {
|
||||
// Asynchronously load templates located in separate .html files
|
||||
loadTemplate:function (views, callback) {
|
||||
|
||||
var deferreds = [];
|
||||
|
||||
$.each(views, function (index, view) {
|
||||
if (window[view]) {
|
||||
deferreds.push($.get('/tpl/' + view + '/', function (data) {
|
||||
window[view].prototype.template = _.template(data);
|
||||
}));
|
||||
} else {
|
||||
alert(view + " not found");
|
||||
}
|
||||
});
|
||||
|
||||
$.when.apply(null, deferreds).done(callback);
|
||||
},
|
||||
|
||||
uploadFile:function (file, callbackSuccess) {
|
||||
var self = this;
|
||||
var data = new FormData();
|
||||
data.append('file', file);
|
||||
$.ajax({
|
||||
url:'api/upload.php',
|
||||
type:'POST',
|
||||
data:data,
|
||||
processData:false,
|
||||
cache:false,
|
||||
contentType:false
|
||||
})
|
||||
.done(function () {
|
||||
console.log(file.name + " uploaded successfully");
|
||||
callbackSuccess();
|
||||
})
|
||||
.fail(function () {
|
||||
self.showAlert('Error!', 'An error occurred while uploading ' + file.name, 'alert-error');
|
||||
});
|
||||
},
|
||||
|
||||
displayValidationErrors:function (messages) {
|
||||
for (var key in messages) {
|
||||
if (messages.hasOwnProperty(key)) {
|
||||
this.addValidationError(key, messages[key]);
|
||||
}
|
||||
}
|
||||
this.showAlert('Warning!', 'Fix validation errors and try again', 'alert-warning');
|
||||
},
|
||||
|
||||
addValidationError:function (field, message) {
|
||||
var controlGroup = $('#' + field).parent().parent();
|
||||
controlGroup.addClass('error');
|
||||
$('.help-inline', controlGroup).html(message);
|
||||
},
|
||||
|
||||
removeValidationError:function (field) {
|
||||
var controlGroup = $('#' + field).parent().parent();
|
||||
controlGroup.removeClass('error');
|
||||
$('.help-inline', controlGroup).html('');
|
||||
},
|
||||
|
||||
showAlert:function (title, text, klass) {
|
||||
$('.alert').removeClass("alert-error alert-warning alert-success alert-info");
|
||||
$('.alert').addClass(klass);
|
||||
$('.alert').html('<strong>' + title + '</strong> ' + text);
|
||||
$('.alert').show();
|
||||
},
|
||||
|
||||
hideAlert:function () {
|
||||
$('.alert').hide();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
window.TastypieModel = Backbone.Model.extend({
|
||||
base_url:function () {
|
||||
var temp_url = Backbone.Model.prototype.url.call(this);
|
||||
return (temp_url.charAt(temp_url.length - 1) == '/' ? temp_url : temp_url + '/');
|
||||
},
|
||||
url:function () {
|
||||
return this.base_url();
|
||||
}
|
||||
});
|
||||
|
||||
window.TastypieCollection = Backbone.Collection.extend({
|
||||
parse:function (response) {
|
||||
this.recent_meta = response.meta || {};
|
||||
return response.objects || response;
|
||||
}
|
||||
});
|
||||
46
static/js/app/views/comment.js
Normal file
@@ -0,0 +1,46 @@
|
||||
window.CommentItemView = Backbone.View.extend({
|
||||
tagName:"li",
|
||||
initialize:function () {
|
||||
$(this.el).data("id", this.model.get("id"));
|
||||
$(this.el).addClass("comment-entry");
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template({"item":this.model.toJSON()}));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
window.CommentListView = Backbone.View.extend({
|
||||
initialize:function () {
|
||||
//this.collection.bind('add', this.render);
|
||||
},
|
||||
events:{
|
||||
"click #id-btn-add-comment":"addComment"
|
||||
},
|
||||
addComment:function (ev) {
|
||||
var comment = $('textarea[name=new-comment]').val();
|
||||
var view = this;
|
||||
if (comment) {
|
||||
this.collection.create({
|
||||
mix:this.collection.mix,
|
||||
comment:comment,
|
||||
time_index:15
|
||||
}, {
|
||||
success:function () {
|
||||
view.collection.sort();
|
||||
view.render();
|
||||
},
|
||||
error:function () {
|
||||
utils.showAlert("Error", "Unable to save comment", "alert-error");
|
||||
}});
|
||||
$('textarea[name=new-comment]').val('');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template()).append('<ul class="comment-listing list-nostyle"></ul>');
|
||||
this.collection.each(function (item) {
|
||||
$('.comment-listing', this.el).append(new CommentItemView({model:item}).render().el);
|
||||
}, this);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
54
static/js/app/views/header.js
Normal file
@@ -0,0 +1,54 @@
|
||||
window.HeaderView = Backbone.View.extend({
|
||||
events:{
|
||||
"click #header-play-pause-button":"togglePlayState",
|
||||
"click #header-live-button":"playLive"
|
||||
},
|
||||
initialize:function () {
|
||||
this.render();
|
||||
_.bindAll(this, "trackChanged");
|
||||
_.bindAll(this, "trackPlaying");
|
||||
_.bindAll(this, "trackPaused");
|
||||
_eventAggregator.bind("track_changed", this.trackChanged);
|
||||
_eventAggregator.bind("track_playing", this.trackPlaying);
|
||||
_eventAggregator.bind("track_paused", this.trackPaused);
|
||||
},
|
||||
trackChanged:function (data) {
|
||||
$(this.el).find('#track-description').text(data.title);
|
||||
},
|
||||
trackPlaying:function (data) {
|
||||
$(this.el).find('#header-play-button-icon').removeClass('icon-play');
|
||||
$(this.el).find('#header-play-button-icon').addClass('icon-pause');
|
||||
},
|
||||
trackPaused:function (data) {
|
||||
$(this.el).find('#header-play-button-icon').removeClass('icon-pause');
|
||||
$(this.el).find('#header-play-button-icon').addClass('icon-play');
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template());
|
||||
return this;
|
||||
},
|
||||
playLive:function () {
|
||||
dssSoundHandler.playLive();
|
||||
_eventAggregator.trigger("track_playing")
|
||||
var button = $(this.el).find('#header-play-pause-button');
|
||||
button.data("mode", "pause");
|
||||
$.getJSON(
|
||||
'ajax/live_now_playing/',
|
||||
function (data) {
|
||||
_eventAggregator.trigger("track_changed", data);
|
||||
});
|
||||
},
|
||||
togglePlayState:function () {
|
||||
var button = $(this.el).find('#header-play-pause-button');
|
||||
var mode = button.data("mode");
|
||||
if (mode == "play") {
|
||||
dssSoundHandler.resumeSound();
|
||||
_eventAggregator.trigger("track_playing");
|
||||
button.data("mode", "pause");
|
||||
} else {
|
||||
dssSoundHandler.pauseSound();
|
||||
_eventAggregator.trigger("track_paused");
|
||||
button.data("mode", "play");
|
||||
}
|
||||
}
|
||||
});
|
||||
91
static/js/app/views/mix.js
Normal file
@@ -0,0 +1,91 @@
|
||||
window.MixItemView = Backbone.View.extend({
|
||||
tagName:"li",
|
||||
events:{
|
||||
"click .play-button-small":"playMix",
|
||||
"click .like-button a":"likeMix",
|
||||
"click .share-button a":"shareLink"
|
||||
},
|
||||
initialize:function () {
|
||||
$(this.el).attr("id", "mixitem-" + this.model.get("id"));
|
||||
$(this.el).addClass("audio-listing-item");
|
||||
$(this.el).data("id", this.model.get("id"));
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template({"item":this.model.toJSON()}));
|
||||
return this;
|
||||
},
|
||||
shareLink:function (e) {
|
||||
alert("Sharing");
|
||||
},
|
||||
likeMix:function (e) {
|
||||
var id = $(e.currentTarget).data("id");
|
||||
var mode = $(e.currentTarget).data("mode");
|
||||
$.post(
|
||||
"/ajax/like/",
|
||||
{ dataId:id, dataMode:mode },
|
||||
function (data) {
|
||||
alert(data);
|
||||
}
|
||||
);
|
||||
},
|
||||
playMix:function () {
|
||||
var id = $(this.el).data("id");
|
||||
var mode = "play";
|
||||
|
||||
//check if we're currently playing a sound
|
||||
var playingId = dssSoundHandler.getPlayingId();
|
||||
if (playingId != -1 && dssSoundHandler.isPlaying()) {
|
||||
var newMode = dssSoundHandler.togglePlaying(playingId);
|
||||
//only set the mode if we're toggling an existing track
|
||||
//otherwise default mode of "play" is what we want
|
||||
if (playingId == id)
|
||||
mode = newMode;
|
||||
}
|
||||
var button = $(this.el).find('#play-pause-button-small-' + id);
|
||||
$(button).blur();
|
||||
if (mode == "play") {
|
||||
dssSoundHandler.togglePlayVisual(id);
|
||||
$.getJSON(
|
||||
'ajax/mix_stream_url/' + id + '/',
|
||||
function (data) {
|
||||
dssSoundHandler.playSound(id, data.stream_url);
|
||||
_eventAggregator.trigger("track_changed", data);
|
||||
_eventAggregator.trigger("track_playing");
|
||||
});
|
||||
} else if (mode == "resume") {
|
||||
_eventAggregator.trigger("track_playing");
|
||||
} else {
|
||||
_eventAggregator.trigger("track_paused");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.MixListView = Backbone.View.extend({
|
||||
initialize:function () {
|
||||
this.render();
|
||||
},
|
||||
render:function () {
|
||||
var mixes = this.collection;
|
||||
var el = this.el;
|
||||
$(this.el).html(this.template()).append('<ul class="mix-listing audio-listing"></ul>');
|
||||
this.collection.each(function (item) {
|
||||
$('.mix-listing', el).append(new MixItemView({model:item}).render().el);
|
||||
});
|
||||
var type = this.collection.type;
|
||||
$('#' + type, this.el).parent().addClass('active');
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
window.MixView = Backbone.View.extend({
|
||||
initialize:function () {
|
||||
this.render();
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template());
|
||||
var item = new MixItemView({model:this.model}).render();
|
||||
$('.mix-listing', this.el).append(item.el);
|
||||
$('#mix-description', this.el).html(this.model.get("description"));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
37
static/js/app/views/release.js
Normal file
@@ -0,0 +1,37 @@
|
||||
var ReleaseItemView = Backbone.View.extend({
|
||||
tagName:"li",
|
||||
initialize:function () {
|
||||
/*$(this.el).attr("id", "releaseitem-" + this.model.get("id"));
|
||||
$(this.el).addClass("release-listing-item");
|
||||
$(this.el).data("id", this.model.get("id"));*/
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template({"item":this.model.toJSON()}));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
var ReleaseListView = Backbone.View.extend({
|
||||
initialize:function () {
|
||||
this.render();
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template()).append('<ul class="release-listing audio-listing"></ul>');
|
||||
var el = this.el;
|
||||
this.collection.each(function (item) {
|
||||
$('.release-listing', el).append(new ReleaseItemView({model:item}).render().el);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
});
|
||||
var ReleaseView = Backbone.View.extend({
|
||||
initialize:function () {
|
||||
this.render();
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template());
|
||||
var item = new ReleaseItemView({model:this.model}).render();
|
||||
$('.release-listing', this.el).append(item.el);
|
||||
$('#release-description', this.el).html(this.model.get("description"));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
40
static/js/app/views/release_audio.js
Normal file
@@ -0,0 +1,40 @@
|
||||
window.ReleaseAudioItemView = Backbone.View.extend({
|
||||
tagName:"li",
|
||||
initialize:function () {
|
||||
$(this.el).data("id", this.model.get("id"));
|
||||
$(this.el).addClass("release-audio-entry");
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template({"item":this.model.toJSON()}));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
window.ReleaseAudioListView = Backbone.View.extend({
|
||||
initialize:function () {
|
||||
this.render();
|
||||
|
||||
},
|
||||
events: {
|
||||
"click a": "clicked"
|
||||
},
|
||||
clicked: function(e){
|
||||
e.preventDefault();
|
||||
this.renderItem($(e.currentTarget).attr("id"));
|
||||
},
|
||||
render:function () {
|
||||
$(this.el).html(this.template()).append('<ul class="release-audio-listing audio-listing"></ul>');
|
||||
var i=0;
|
||||
this.collection.each(function (item) {
|
||||
$('#release-audio-slide-nav', this.el).append('<li><a id="' + i++ + '"" class="selector-button" href="' + item.get('resource_uri') + '">' + (i) + '</a></li>');
|
||||
}, this);
|
||||
this.renderItem(0);
|
||||
return this;
|
||||
},
|
||||
renderItem: function(id){
|
||||
$('.release-audio-listing', this.el).empty();
|
||||
$('#' + id, this.el).blur();
|
||||
$('#' + id, this.el).addClass('on');
|
||||
$('a:not([id="' + id + '"]', this.el).removeClass('on');
|
||||
$('.release-audio-listing', this.el).append(new ReleaseAudioItemView({model:this.collection.models[id]}).render().el);
|
||||
}
|
||||
});
|
||||
16
static/js/app/views/sidebar.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Created with PyCharm.
|
||||
* User: fergalm
|
||||
* Date: 08/08/12
|
||||
* Time: 22:10
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
window.SidebarView = Backbone.View.extend({
|
||||
initialize: function(){
|
||||
this.render();
|
||||
},
|
||||
render: function(){
|
||||
$(this.el).html(this.template());
|
||||
return this;
|
||||
}
|
||||
});
|
||||
137
static/js/dss_sound_handler.js
Normal file
@@ -0,0 +1,137 @@
|
||||
$(document).ready(function () {
|
||||
soundManager.url = '/static/bin/sm/';
|
||||
soundManager.flashVersion = 10;
|
||||
soundManager.debugMode = false;
|
||||
soundManager.useHTML5Audio = true;
|
||||
});
|
||||
|
||||
function DssSoundHandler() {
|
||||
var _currentSound = null;
|
||||
var _currentId = -1;
|
||||
this.stop_sound = function () {
|
||||
if (_currentSound) {
|
||||
this.togglePlaying(this.getPlayingId());
|
||||
_currentSound.stop();
|
||||
_currentId = -1;
|
||||
}
|
||||
};
|
||||
this.getPlayingId = function () {
|
||||
return _currentId;
|
||||
};
|
||||
this.isPlaying = function () {
|
||||
if (_currentSound != null)
|
||||
return _currentSound.playState == 1;
|
||||
};
|
||||
|
||||
this.togglePlaying = function (id) {
|
||||
this.togglePlayState(id);
|
||||
return this.togglePlayVisual(id);
|
||||
};
|
||||
|
||||
this.togglePlayVisual = function (id) {
|
||||
var button = $('#play-pause-button-small-' + id);
|
||||
var mode = button.data("mode");
|
||||
if (mode == "play" || mode == "resume") {
|
||||
button.data('mode', 'pause');
|
||||
button.removeClass('play-button-small-playing');
|
||||
button.addClass('play-button-small-paused');
|
||||
} else {
|
||||
button.data('mode', 'resume');
|
||||
button.removeClass('play-button-small-paused');
|
||||
button.addClass('play-button-small-playing');
|
||||
}
|
||||
return mode;
|
||||
};
|
||||
this.togglePlayState = function (id) {
|
||||
var button = $('#play-pause-button-small-' + id);
|
||||
var mode = button.data("mode");
|
||||
if (mode == 'pause')
|
||||
this.pauseSound();
|
||||
else if (mode == 'resume')
|
||||
this.resumeSound();
|
||||
};
|
||||
this.playSound = (function (itemId, stream_url) {
|
||||
|
||||
_currentId = itemId;
|
||||
var waveformTop = $('#waveform-' + _currentId).position().top;
|
||||
var waveformWidth = $('#waveform-' + _currentId).width();
|
||||
|
||||
$('#player-seekhead').css('top', waveformTop);
|
||||
if (_currentSound) _currentSound.stop();
|
||||
soundManager.destroySound('current_sound');
|
||||
_currentSound = soundManager.createSound({
|
||||
id:'current_sound',
|
||||
url:stream_url,
|
||||
volume:50,
|
||||
stream:true,
|
||||
whileloading:function () {
|
||||
var percentageFinished = (_currentSound.bytesLoaded / _currentSound.bytesTotal) * 100;
|
||||
var percentageWidth = (waveformWidth / 100) * percentageFinished;
|
||||
$('#progress-player-' + _currentId).css('width', percentageWidth);
|
||||
},
|
||||
whileplaying:function () {
|
||||
/* Should move to an aggregator viewChanged callback */
|
||||
waveformTop = $('#waveform-' + _currentId).position().top;
|
||||
waveformWidth = $('#waveform-' + _currentId).width();
|
||||
$('#playhead-player-' + _currentId).css('top', waveformTop);
|
||||
$('#progress-player-' + _currentId).css('top', waveformTop);
|
||||
$('#player-seekhead').css('top', waveformTop);
|
||||
|
||||
var currentPosition = _currentSound.position;
|
||||
var totalLength = _currentSound.duration;
|
||||
var percentageFinished = (currentPosition / totalLength) * 100;
|
||||
var percentageWidth = (waveformWidth / 100) * percentageFinished;
|
||||
$('#playhead-player-' + _currentId).css('width', percentageWidth);
|
||||
}
|
||||
});
|
||||
_currentSound.loaded = false;
|
||||
_currentSound.readyState = 0;
|
||||
dssSoundHandler._setupEvents(_currentId, _currentSound);
|
||||
_currentSound.play();
|
||||
});
|
||||
this.playLive = function () {
|
||||
this.stop_sound();
|
||||
_currentSound = soundManager.createSound({
|
||||
id:'current_sound',
|
||||
url:'http://streams.deepsouthsounds.com:8000/live',
|
||||
volume:50,
|
||||
type:'audio/mp3'
|
||||
});
|
||||
_currentSound.play();
|
||||
};
|
||||
this.pauseSound = function () {
|
||||
this.togglePlaying();
|
||||
if (_currentSound) {
|
||||
_currentSound.pause();
|
||||
}
|
||||
};
|
||||
this.resumeSound = function () {
|
||||
this.togglePlaying();
|
||||
if (_currentSound)
|
||||
_currentSound.resume();
|
||||
};
|
||||
this.getPosition = function () {
|
||||
if (_currentSound)
|
||||
return _currentSound.position;
|
||||
};
|
||||
this._setupEvents = function (itemId, sound) {
|
||||
$('#waveform-' + itemId).mousemove(function (event) {
|
||||
$('#player-seekhead').show();
|
||||
$('#player-seekhead').css('left', (event.pageX) + 'px').fadeIn('fast');
|
||||
});
|
||||
$('#waveform-' + itemId).mousedown(function (event) {
|
||||
var width = $('#waveform-image-' + itemId).width();
|
||||
if (sound != null) {
|
||||
var fullLength = sound.duration;
|
||||
var left = $('#waveform-image-' + itemId).offset().left;
|
||||
var clickPerc = ((event.pageX - left) / width) * 100;
|
||||
sound.setPosition((fullLength / 100) * clickPerc);
|
||||
}
|
||||
});
|
||||
$('#waveform-' + itemId).mouseleave(function (event) {
|
||||
$('#player-seekhead').hide();
|
||||
});
|
||||
};
|
||||
}
|
||||
dssSoundHandler = new DssSoundHandler();
|
||||
window.dssSoundHandler = dssSoundHandler;
|
||||
543
static/js/libs/ICanHaz.js
Normal file
@@ -0,0 +1,543 @@
|
||||
/*!
|
||||
ICanHaz.js version 0.10 -- by @HenrikJoreteg
|
||||
More info at: http://icanhazjs.com
|
||||
*/
|
||||
(function () {
|
||||
/*
|
||||
mustache.js — Logic-less templates in JavaScript
|
||||
|
||||
See http://mustache.github.com/ for more info.
|
||||
*/
|
||||
|
||||
var Mustache = function () {
|
||||
var _toString = Object.prototype.toString;
|
||||
|
||||
Array.isArray = Array.isArray || function (obj) {
|
||||
return _toString.call(obj) == "[object Array]";
|
||||
}
|
||||
|
||||
var _trim = String.prototype.trim, trim;
|
||||
|
||||
if (_trim) {
|
||||
trim = function (text) {
|
||||
return text == null ? "" : _trim.call(text);
|
||||
}
|
||||
} else {
|
||||
var trimLeft, trimRight;
|
||||
|
||||
// IE doesn't match non-breaking spaces with \s.
|
||||
if ((/\S/).test("\xA0")) {
|
||||
trimLeft = /^[\s\xA0]+/;
|
||||
trimRight = /[\s\xA0]+$/;
|
||||
} else {
|
||||
trimLeft = /^\s+/;
|
||||
trimRight = /\s+$/;
|
||||
}
|
||||
|
||||
trim = function (text) {
|
||||
return text == null ? "" :
|
||||
text.toString().replace(trimLeft, "").replace(trimRight, "");
|
||||
}
|
||||
}
|
||||
|
||||
var escapeMap = {
|
||||
"&":"&",
|
||||
"<":"<",
|
||||
">":">",
|
||||
'"':'"',
|
||||
"'":'''
|
||||
};
|
||||
|
||||
function escapeHTML(string) {
|
||||
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
|
||||
return escapeMap[s] || s;
|
||||
});
|
||||
}
|
||||
|
||||
var regexCache = {};
|
||||
var Renderer = function () {
|
||||
};
|
||||
|
||||
Renderer.prototype = {
|
||||
otag:"{{",
|
||||
ctag:"}}",
|
||||
pragmas:{},
|
||||
buffer:[],
|
||||
pragmas_implemented:{
|
||||
"IMPLICIT-ITERATOR":true
|
||||
},
|
||||
context:{},
|
||||
|
||||
render:function (template, context, partials, in_recursion) {
|
||||
// reset buffer & set context
|
||||
if (!in_recursion) {
|
||||
this.context = context;
|
||||
this.buffer = []; // TODO: make this non-lazy
|
||||
}
|
||||
|
||||
// fail fast
|
||||
if (!this.includes("", template)) {
|
||||
if (in_recursion) {
|
||||
return template;
|
||||
} else {
|
||||
this.send(template);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// get the pragmas together
|
||||
template = this.render_pragmas(template);
|
||||
|
||||
// render the template
|
||||
var html = this.render_section(template, context, partials);
|
||||
|
||||
// render_section did not find any sections, we still need to render the tags
|
||||
if (html === false) {
|
||||
html = this.render_tags(template, context, partials, in_recursion);
|
||||
}
|
||||
|
||||
if (in_recursion) {
|
||||
return html;
|
||||
} else {
|
||||
this.sendLines(html);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
Sends parsed lines
|
||||
*/
|
||||
send:function (line) {
|
||||
if (line !== "") {
|
||||
this.buffer.push(line);
|
||||
}
|
||||
},
|
||||
|
||||
sendLines:function (text) {
|
||||
if (text) {
|
||||
var lines = text.split("\n");
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
this.send(lines[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
Looks for %PRAGMAS
|
||||
*/
|
||||
render_pragmas:function (template) {
|
||||
// no pragmas
|
||||
if (!this.includes("%", template)) {
|
||||
return template;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) {
|
||||
return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
|
||||
});
|
||||
|
||||
return template.replace(regex, function (match, pragma, options) {
|
||||
if (!that.pragmas_implemented[pragma]) {
|
||||
throw({message:"This implementation of mustache doesn't understand the '" +
|
||||
pragma + "' pragma"});
|
||||
}
|
||||
that.pragmas[pragma] = {};
|
||||
if (options) {
|
||||
var opts = options.split("=");
|
||||
that.pragmas[pragma][opts[0]] = opts[1];
|
||||
}
|
||||
return "";
|
||||
// ignore unknown pragmas silently
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
Tries to find a partial in the curent scope and render it
|
||||
*/
|
||||
render_partial:function (name, context, partials) {
|
||||
name = trim(name);
|
||||
if (!partials || partials[name] === undefined) {
|
||||
throw({message:"unknown_partial '" + name + "'"});
|
||||
}
|
||||
if (!context || typeof context[name] != "object") {
|
||||
return this.render(partials[name], context, partials, true);
|
||||
}
|
||||
return this.render(partials[name], context[name], partials, true);
|
||||
},
|
||||
|
||||
/*
|
||||
Renders inverted (^) and normal (#) sections
|
||||
*/
|
||||
render_section:function (template, context, partials) {
|
||||
if (!this.includes("#", template) && !this.includes("^", template)) {
|
||||
// did not render anything, there were no sections
|
||||
return false;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
|
||||
var regex = this.getCachedRegex("render_section", function (otag, ctag) {
|
||||
// This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
|
||||
return new RegExp(
|
||||
"^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
|
||||
|
||||
otag + // {{
|
||||
"(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3)
|
||||
ctag + // }}
|
||||
|
||||
"\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
|
||||
|
||||
otag + // {{
|
||||
"\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
|
||||
ctag + // }}
|
||||
|
||||
"\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
|
||||
|
||||
"g");
|
||||
});
|
||||
|
||||
|
||||
// for each {{#foo}}{{/foo}} section do...
|
||||
return template.replace(regex, function (match, before, type, name, content, after) {
|
||||
// before contains only tags, no sections
|
||||
var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
|
||||
|
||||
// after may contain both sections and tags, so use full rendering function
|
||||
renderedAfter = after ? that.render(after, context, partials, true) : "",
|
||||
|
||||
// will be computed below
|
||||
renderedContent,
|
||||
|
||||
value = that.find(name, context);
|
||||
|
||||
if (type === "^") { // inverted section
|
||||
if (!value || Array.isArray(value) && value.length === 0) {
|
||||
// false or empty list, render it
|
||||
renderedContent = that.render(content, context, partials, true);
|
||||
} else {
|
||||
renderedContent = "";
|
||||
}
|
||||
} else if (type === "#") { // normal section
|
||||
if (Array.isArray(value)) { // Enumerable, Let's loop!
|
||||
renderedContent = that.map(value,function (row) {
|
||||
return that.render(content, that.create_context(row), partials, true);
|
||||
}).join("");
|
||||
} else if (that.is_object(value)) { // Object, Use it as subcontext!
|
||||
renderedContent = that.render(content, that.create_context(value),
|
||||
partials, true);
|
||||
} else if (typeof value == "function") {
|
||||
// higher order section
|
||||
renderedContent = value.call(context, content, function (text) {
|
||||
return that.render(text, context, partials, true);
|
||||
});
|
||||
} else if (value) { // boolean section
|
||||
renderedContent = that.render(content, context, partials, true);
|
||||
} else {
|
||||
renderedContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
return renderedBefore + renderedContent + renderedAfter;
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
Replace {{foo}} and friends with values from our view
|
||||
*/
|
||||
render_tags:function (template, context, partials, in_recursion) {
|
||||
// tit for tat
|
||||
var that = this;
|
||||
|
||||
var new_regex = function () {
|
||||
return that.getCachedRegex("render_tags", function (otag, ctag) {
|
||||
return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g");
|
||||
});
|
||||
};
|
||||
|
||||
var regex = new_regex();
|
||||
var tag_replace_callback = function (match, operator, name) {
|
||||
switch (operator) {
|
||||
case "!": // ignore comments
|
||||
return "";
|
||||
case "=": // set new delimiters, rebuild the replace regexp
|
||||
that.set_delimiters(name);
|
||||
regex = new_regex();
|
||||
return "";
|
||||
case ">": // render partial
|
||||
return that.render_partial(name, context, partials);
|
||||
case "{": // the triple mustache is unescaped
|
||||
case "&": // & operator is an alternative unescape method
|
||||
return that.find(name, context);
|
||||
default: // escape the value
|
||||
return escapeHTML(that.find(name, context));
|
||||
}
|
||||
};
|
||||
var lines = template.split("\n");
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].replace(regex, tag_replace_callback, this);
|
||||
if (!in_recursion) {
|
||||
this.send(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (in_recursion) {
|
||||
return lines.join("\n");
|
||||
}
|
||||
},
|
||||
|
||||
set_delimiters:function (delimiters) {
|
||||
var dels = delimiters.split(" ");
|
||||
this.otag = this.escape_regex(dels[0]);
|
||||
this.ctag = this.escape_regex(dels[1]);
|
||||
},
|
||||
|
||||
escape_regex:function (text) {
|
||||
// thank you Simon Willison
|
||||
if (!arguments.callee.sRE) {
|
||||
var specials = [
|
||||
'/', '.', '*', '+', '?', '|',
|
||||
'(', ')', '[', ']', '{', '}', '\\'
|
||||
];
|
||||
arguments.callee.sRE = new RegExp(
|
||||
'(\\' + specials.join('|\\') + ')', 'g'
|
||||
);
|
||||
}
|
||||
return text.replace(arguments.callee.sRE, '\\$1');
|
||||
},
|
||||
|
||||
/*
|
||||
find `name` in current `context`. That is find me a value
|
||||
from the view object
|
||||
*/
|
||||
find:function (name, context) {
|
||||
name = trim(name);
|
||||
|
||||
// Checks whether a value is thruthy or false or 0
|
||||
function is_kinda_truthy(bool) {
|
||||
return bool === false || bool === 0 || bool;
|
||||
}
|
||||
|
||||
var value;
|
||||
|
||||
// check for dot notation eg. foo.bar
|
||||
if (name.match(/([a-z_]+)\./ig)) {
|
||||
var childValue = this.walk_context(name, context);
|
||||
if (is_kinda_truthy(childValue)) {
|
||||
value = childValue;
|
||||
}
|
||||
} else {
|
||||
if (is_kinda_truthy(context[name])) {
|
||||
value = context[name];
|
||||
} else if (is_kinda_truthy(this.context[name])) {
|
||||
value = this.context[name];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value == "function") {
|
||||
return value.apply(context);
|
||||
}
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
// silently ignore unkown variables
|
||||
return "";
|
||||
},
|
||||
|
||||
walk_context:function (name, context) {
|
||||
var path = name.split('.');
|
||||
// if the var doesn't exist in current context, check the top level context
|
||||
var value_context = (context[path[0]] != undefined) ? context : this.context;
|
||||
var value = value_context[path.shift()];
|
||||
while (value != undefined && path.length > 0) {
|
||||
value_context = value;
|
||||
value = value[path.shift()];
|
||||
}
|
||||
// if the value is a function, call it, binding the correct context
|
||||
if (typeof value == "function") {
|
||||
return value.apply(value_context);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
// Utility methods
|
||||
|
||||
/* includes tag */
|
||||
includes:function (needle, haystack) {
|
||||
return haystack.indexOf(this.otag + needle) != -1;
|
||||
},
|
||||
|
||||
// by @langalex, support for arrays of strings
|
||||
create_context:function (_context) {
|
||||
if (this.is_object(_context)) {
|
||||
return _context;
|
||||
} else {
|
||||
var iterator = ".";
|
||||
if (this.pragmas["IMPLICIT-ITERATOR"]) {
|
||||
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
|
||||
}
|
||||
var ctx = {};
|
||||
ctx[iterator] = _context;
|
||||
return ctx;
|
||||
}
|
||||
},
|
||||
|
||||
is_object:function (a) {
|
||||
return a && typeof a == "object";
|
||||
},
|
||||
|
||||
/*
|
||||
Why, why, why? Because IE. Cry, cry cry.
|
||||
*/
|
||||
map:function (array, fn) {
|
||||
if (typeof array.map == "function") {
|
||||
return array.map(fn);
|
||||
} else {
|
||||
var r = [];
|
||||
var l = array.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
r.push(fn(array[i]));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
},
|
||||
|
||||
getCachedRegex:function (name, generator) {
|
||||
var byOtag = regexCache[this.otag];
|
||||
if (!byOtag) {
|
||||
byOtag = regexCache[this.otag] = {};
|
||||
}
|
||||
|
||||
var byCtag = byOtag[this.ctag];
|
||||
if (!byCtag) {
|
||||
byCtag = byOtag[this.ctag] = {};
|
||||
}
|
||||
|
||||
var regex = byCtag[name];
|
||||
if (!regex) {
|
||||
regex = byCtag[name] = generator(this.otag, this.ctag);
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
};
|
||||
|
||||
return({
|
||||
name:"mustache.js",
|
||||
version:"0.4.0",
|
||||
|
||||
/*
|
||||
Turns a template and view into HTML
|
||||
*/
|
||||
to_html:function (template, view, partials, send_fun) {
|
||||
var renderer = new Renderer();
|
||||
if (send_fun) {
|
||||
renderer.send = send_fun;
|
||||
}
|
||||
renderer.render(template, view || {}, partials);
|
||||
if (!send_fun) {
|
||||
return renderer.buffer.join("\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
}();
|
||||
/*!
|
||||
ICanHaz.js -- by @HenrikJoreteg
|
||||
*/
|
||||
/*global */
|
||||
(function () {
|
||||
function trim(stuff) {
|
||||
if (''.trim) return stuff.trim();
|
||||
else return stuff.replace(/^\s+/, '').replace(/\s+$/, '');
|
||||
}
|
||||
|
||||
var ich = {
|
||||
VERSION:"0.10",
|
||||
templates:{},
|
||||
|
||||
// grab jquery or zepto if it's there
|
||||
$:(typeof window !== 'undefined') ? window.jQuery || window.Zepto || null : null,
|
||||
|
||||
// public function for adding templates
|
||||
// can take a name and template string arguments
|
||||
// or can take an object with name/template pairs
|
||||
// We're enforcing uniqueness to avoid accidental template overwrites.
|
||||
// If you want a different template, it should have a different name.
|
||||
addTemplate:function (name, templateString) {
|
||||
if (typeof name === 'object') {
|
||||
for (var template in name) {
|
||||
this.addTemplate(template, name[template]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ich[name]) {
|
||||
console.error("Invalid name: " + name + ".");
|
||||
} else if (ich.templates[name]) {
|
||||
console.error("Template \"" + name + " \" exists");
|
||||
} else {
|
||||
ich.templates[name] = templateString;
|
||||
ich[name] = function (data, raw) {
|
||||
data = data || {};
|
||||
var result = Mustache.to_html(ich.templates[name], data, ich.templates);
|
||||
return (ich.$ && !raw) ? ich.$(result) : result;
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// clears all retrieval functions and empties cache
|
||||
clearAll:function () {
|
||||
for (var key in ich.templates) {
|
||||
delete ich[key];
|
||||
}
|
||||
ich.templates = {};
|
||||
},
|
||||
|
||||
// clears/grabs
|
||||
refresh:function () {
|
||||
ich.clearAll();
|
||||
ich.grabTemplates();
|
||||
},
|
||||
|
||||
// grabs templates from the DOM and caches them.
|
||||
// Loop through and add templates.
|
||||
// Whitespace at beginning and end of all templates inside <script> tags will
|
||||
// be trimmed. If you want whitespace around a partial, add it in the parent,
|
||||
// not the partial. Or do it explicitly using <br/> or
|
||||
grabTemplates:function () {
|
||||
var i,
|
||||
scripts = document.getElementsByTagName('script'),
|
||||
script,
|
||||
trash = [];
|
||||
for (i = 0, l = scripts.length; i < l; i++) {
|
||||
script = scripts[i];
|
||||
if (script && script.innerHTML && script.id && (script.type === "text/html" || script.type === "text/x-icanhaz")) {
|
||||
ich.addTemplate(script.id, trim(script.innerHTML));
|
||||
trash.unshift(script);
|
||||
}
|
||||
}
|
||||
for (i = 0, l = trash.length; i < l; i++) {
|
||||
trash[i].parentNode.removeChild(trash[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Use CommonJS if applicable
|
||||
if (typeof require !== 'undefined') {
|
||||
module.exports = ich;
|
||||
} else {
|
||||
// else attach it to the window
|
||||
window.ich = ich;
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
if (ich.$) {
|
||||
ich.$(function () {
|
||||
ich.grabTemplates();
|
||||
});
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
ich.grabTemplates();
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
})();
|
||||
84
static/js/libs/backbone/backbone-localstorage.js
Normal file
@@ -0,0 +1,84 @@
|
||||
// A simple module to replace `Backbone.sync` with *localStorage*-based
|
||||
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
|
||||
// as that.
|
||||
|
||||
// Generate four random hex digits.
|
||||
function S4() {
|
||||
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
|
||||
};
|
||||
|
||||
// Generate a pseudo-GUID by concatenating random hexadecimal.
|
||||
function guid() {
|
||||
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
|
||||
};
|
||||
|
||||
// Our Store is represented by a single JS object in *localStorage*. Create it
|
||||
// with a meaningful name, like the name you'd give a table.
|
||||
var Store = function(name) {
|
||||
this.name = name;
|
||||
var store = localStorage.getItem(this.name);
|
||||
this.data = (store && JSON.parse(store)) || {};
|
||||
};
|
||||
|
||||
_.extend(Store.prototype, {
|
||||
|
||||
// Save the current state of the **Store** to *localStorage*.
|
||||
save: function() {
|
||||
localStorage.setItem(this.name, JSON.stringify(this.data));
|
||||
},
|
||||
|
||||
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
|
||||
// have an id of it's own.
|
||||
create: function(model) {
|
||||
if (!model.id) model.id = model.attributes.id = guid();
|
||||
this.data[model.id] = model;
|
||||
this.save();
|
||||
return model;
|
||||
},
|
||||
|
||||
// Update a model by replacing its copy in `this.data`.
|
||||
update: function(model) {
|
||||
this.data[model.id] = model;
|
||||
this.save();
|
||||
return model;
|
||||
},
|
||||
|
||||
// Retrieve a model from `this.data` by id.
|
||||
find: function(model) {
|
||||
return this.data[model.id];
|
||||
},
|
||||
|
||||
// Return the array of all models currently in storage.
|
||||
findAll: function() {
|
||||
return _.values(this.data);
|
||||
},
|
||||
|
||||
// Delete a model from `this.data`, returning it.
|
||||
destroy: function(model) {
|
||||
delete this.data[model.id];
|
||||
this.save();
|
||||
return model;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Override `Backbone.sync` to use delegate to the model or collection's
|
||||
// *localStorage* property, which should be an instance of `Store`.
|
||||
Backbone.sync = function(method, model, options) {
|
||||
|
||||
var resp;
|
||||
var store = model.localStorage || model.collection.localStorage;
|
||||
|
||||
switch (method) {
|
||||
case "read": resp = model.id ? store.find(model) : store.findAll(); break;
|
||||
case "create": resp = store.create(model); break;
|
||||
case "update": resp = store.update(model); break;
|
||||
case "delete": resp = store.destroy(model); break;
|
||||
}
|
||||
|
||||
if (resp) {
|
||||
options.success(resp);
|
||||
} else {
|
||||
options.error("Record not found");
|
||||
}
|
||||
};
|
||||
1290
static/js/libs/backbone/backbone.js
Normal file
999
static/js/libs/backbone/underscore.js
Normal file
@@ -0,0 +1,999 @@
|
||||
// Underscore.js 1.3.1
|
||||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
// Underscore is freely distributable under the MIT license.
|
||||
// Portions of Underscore are inspired or borrowed from Prototype,
|
||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
||||
// For all details and documentation:
|
||||
// http://documentcloud.github.com/underscore
|
||||
|
||||
(function() {
|
||||
|
||||
// Baseline setup
|
||||
// --------------
|
||||
|
||||
// Establish the root object, `window` in the browser, or `global` on the server.
|
||||
var root = this;
|
||||
|
||||
// Save the previous value of the `_` variable.
|
||||
var previousUnderscore = root._;
|
||||
|
||||
// Establish the object that gets returned to break out of a loop iteration.
|
||||
var breaker = {};
|
||||
|
||||
// Save bytes in the minified (but not gzipped) version:
|
||||
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
|
||||
|
||||
// Create quick reference variables for speed access to core prototypes.
|
||||
var slice = ArrayProto.slice,
|
||||
unshift = ArrayProto.unshift,
|
||||
toString = ObjProto.toString,
|
||||
hasOwnProperty = ObjProto.hasOwnProperty;
|
||||
|
||||
// All **ECMAScript 5** native function implementations that we hope to use
|
||||
// are declared here.
|
||||
var
|
||||
nativeForEach = ArrayProto.forEach,
|
||||
nativeMap = ArrayProto.map,
|
||||
nativeReduce = ArrayProto.reduce,
|
||||
nativeReduceRight = ArrayProto.reduceRight,
|
||||
nativeFilter = ArrayProto.filter,
|
||||
nativeEvery = ArrayProto.every,
|
||||
nativeSome = ArrayProto.some,
|
||||
nativeIndexOf = ArrayProto.indexOf,
|
||||
nativeLastIndexOf = ArrayProto.lastIndexOf,
|
||||
nativeIsArray = Array.isArray,
|
||||
nativeKeys = Object.keys,
|
||||
nativeBind = FuncProto.bind;
|
||||
|
||||
// Create a safe reference to the Underscore object for use below.
|
||||
var _ = function(obj) { return new wrapper(obj); };
|
||||
|
||||
// Export the Underscore object for **Node.js**, with
|
||||
// backwards-compatibility for the old `require()` API. If we're in
|
||||
// the browser, add `_` as a global object via a string identifier,
|
||||
// for Closure Compiler "advanced" mode.
|
||||
if (typeof exports !== 'undefined') {
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
exports = module.exports = _;
|
||||
}
|
||||
exports._ = _;
|
||||
} else {
|
||||
root['_'] = _;
|
||||
}
|
||||
|
||||
// Current version.
|
||||
_.VERSION = '1.3.1';
|
||||
|
||||
// Collection Functions
|
||||
// --------------------
|
||||
|
||||
// The cornerstone, an `each` implementation, aka `forEach`.
|
||||
// Handles objects with the built-in `forEach`, arrays, and raw objects.
|
||||
// Delegates to **ECMAScript 5**'s native `forEach` if available.
|
||||
var each = _.each = _.forEach = function(obj, iterator, context) {
|
||||
if (obj == null) return;
|
||||
if (nativeForEach && obj.forEach === nativeForEach) {
|
||||
obj.forEach(iterator, context);
|
||||
} else if (obj.length === +obj.length) {
|
||||
for (var i = 0, l = obj.length; i < l; i++) {
|
||||
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
|
||||
}
|
||||
} else {
|
||||
for (var key in obj) {
|
||||
if (_.has(obj, key)) {
|
||||
if (iterator.call(context, obj[key], key, obj) === breaker) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Return the results of applying the iterator to each element.
|
||||
// Delegates to **ECMAScript 5**'s native `map` if available.
|
||||
_.map = _.collect = function(obj, iterator, context) {
|
||||
var results = [];
|
||||
if (obj == null) return results;
|
||||
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
results[results.length] = iterator.call(context, value, index, list);
|
||||
});
|
||||
if (obj.length === +obj.length) results.length = obj.length;
|
||||
return results;
|
||||
};
|
||||
|
||||
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
||||
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
|
||||
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
|
||||
var initial = arguments.length > 2;
|
||||
if (obj == null) obj = [];
|
||||
if (nativeReduce && obj.reduce === nativeReduce) {
|
||||
if (context) iterator = _.bind(iterator, context);
|
||||
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
|
||||
}
|
||||
each(obj, function(value, index, list) {
|
||||
if (!initial) {
|
||||
memo = value;
|
||||
initial = true;
|
||||
} else {
|
||||
memo = iterator.call(context, memo, value, index, list);
|
||||
}
|
||||
});
|
||||
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
|
||||
return memo;
|
||||
};
|
||||
|
||||
// The right-associative version of reduce, also known as `foldr`.
|
||||
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
|
||||
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
|
||||
var initial = arguments.length > 2;
|
||||
if (obj == null) obj = [];
|
||||
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
||||
if (context) iterator = _.bind(iterator, context);
|
||||
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
|
||||
}
|
||||
var reversed = _.toArray(obj).reverse();
|
||||
if (context && !initial) iterator = _.bind(iterator, context);
|
||||
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
|
||||
};
|
||||
|
||||
// Return the first value which passes a truth test. Aliased as `detect`.
|
||||
_.find = _.detect = function(obj, iterator, context) {
|
||||
var result;
|
||||
any(obj, function(value, index, list) {
|
||||
if (iterator.call(context, value, index, list)) {
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// Return all the elements that pass a truth test.
|
||||
// Delegates to **ECMAScript 5**'s native `filter` if available.
|
||||
// Aliased as `select`.
|
||||
_.filter = _.select = function(obj, iterator, context) {
|
||||
var results = [];
|
||||
if (obj == null) return results;
|
||||
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
if (iterator.call(context, value, index, list)) results[results.length] = value;
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
// Return all the elements for which a truth test fails.
|
||||
_.reject = function(obj, iterator, context) {
|
||||
var results = [];
|
||||
if (obj == null) return results;
|
||||
each(obj, function(value, index, list) {
|
||||
if (!iterator.call(context, value, index, list)) results[results.length] = value;
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
// Determine whether all of the elements match a truth test.
|
||||
// Delegates to **ECMAScript 5**'s native `every` if available.
|
||||
// Aliased as `all`.
|
||||
_.every = _.all = function(obj, iterator, context) {
|
||||
var result = true;
|
||||
if (obj == null) return result;
|
||||
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// Determine if at least one element in the object matches a truth test.
|
||||
// Delegates to **ECMAScript 5**'s native `some` if available.
|
||||
// Aliased as `any`.
|
||||
var any = _.some = _.any = function(obj, iterator, context) {
|
||||
iterator || (iterator = _.identity);
|
||||
var result = false;
|
||||
if (obj == null) return result;
|
||||
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
if (result || (result = iterator.call(context, value, index, list))) return breaker;
|
||||
});
|
||||
return !!result;
|
||||
};
|
||||
|
||||
// Determine if a given value is included in the array or object using `===`.
|
||||
// Aliased as `contains`.
|
||||
_.include = _.contains = function(obj, target) {
|
||||
var found = false;
|
||||
if (obj == null) return found;
|
||||
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
|
||||
found = any(obj, function(value) {
|
||||
return value === target;
|
||||
});
|
||||
return found;
|
||||
};
|
||||
|
||||
// Invoke a method (with arguments) on every item in a collection.
|
||||
_.invoke = function(obj, method) {
|
||||
var args = slice.call(arguments, 2);
|
||||
return _.map(obj, function(value) {
|
||||
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
|
||||
});
|
||||
};
|
||||
|
||||
// Convenience version of a common use case of `map`: fetching a property.
|
||||
_.pluck = function(obj, key) {
|
||||
return _.map(obj, function(value){ return value[key]; });
|
||||
};
|
||||
|
||||
// Return the maximum element or (element-based computation).
|
||||
_.max = function(obj, iterator, context) {
|
||||
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
|
||||
if (!iterator && _.isEmpty(obj)) return -Infinity;
|
||||
var result = {computed : -Infinity};
|
||||
each(obj, function(value, index, list) {
|
||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
computed >= result.computed && (result = {value : value, computed : computed});
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
// Return the minimum element (or element-based computation).
|
||||
_.min = function(obj, iterator, context) {
|
||||
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
|
||||
if (!iterator && _.isEmpty(obj)) return Infinity;
|
||||
var result = {computed : Infinity};
|
||||
each(obj, function(value, index, list) {
|
||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
computed < result.computed && (result = {value : value, computed : computed});
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
// Shuffle an array.
|
||||
_.shuffle = function(obj) {
|
||||
var shuffled = [], rand;
|
||||
each(obj, function(value, index, list) {
|
||||
if (index == 0) {
|
||||
shuffled[0] = value;
|
||||
} else {
|
||||
rand = Math.floor(Math.random() * (index + 1));
|
||||
shuffled[index] = shuffled[rand];
|
||||
shuffled[rand] = value;
|
||||
}
|
||||
});
|
||||
return shuffled;
|
||||
};
|
||||
|
||||
// Sort the object's values by a criterion produced by an iterator.
|
||||
_.sortBy = function(obj, iterator, context) {
|
||||
return _.pluck(_.map(obj, function(value, index, list) {
|
||||
return {
|
||||
value : value,
|
||||
criteria : iterator.call(context, value, index, list)
|
||||
};
|
||||
}).sort(function(left, right) {
|
||||
var a = left.criteria, b = right.criteria;
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}), 'value');
|
||||
};
|
||||
|
||||
// Groups the object's values by a criterion. Pass either a string attribute
|
||||
// to group by, or a function that returns the criterion.
|
||||
_.groupBy = function(obj, val) {
|
||||
var result = {};
|
||||
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
|
||||
each(obj, function(value, index) {
|
||||
var key = iterator(value, index);
|
||||
(result[key] || (result[key] = [])).push(value);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// Use a comparator function to figure out at what index an object should
|
||||
// be inserted so as to maintain order. Uses binary search.
|
||||
_.sortedIndex = function(array, obj, iterator) {
|
||||
iterator || (iterator = _.identity);
|
||||
var low = 0, high = array.length;
|
||||
while (low < high) {
|
||||
var mid = (low + high) >> 1;
|
||||
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
|
||||
}
|
||||
return low;
|
||||
};
|
||||
|
||||
// Safely convert anything iterable into a real, live array.
|
||||
_.toArray = function(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) return iterable.toArray();
|
||||
if (_.isArray(iterable)) return slice.call(iterable);
|
||||
if (_.isArguments(iterable)) return slice.call(iterable);
|
||||
return _.values(iterable);
|
||||
};
|
||||
|
||||
// Return the number of elements in an object.
|
||||
_.size = function(obj) {
|
||||
return _.toArray(obj).length;
|
||||
};
|
||||
|
||||
// Array Functions
|
||||
// ---------------
|
||||
|
||||
// Get the first element of an array. Passing **n** will return the first N
|
||||
// values in the array. Aliased as `head`. The **guard** check allows it to work
|
||||
// with `_.map`.
|
||||
_.first = _.head = function(array, n, guard) {
|
||||
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
|
||||
};
|
||||
|
||||
// Returns everything but the last entry of the array. Especcialy useful on
|
||||
// the arguments object. Passing **n** will return all the values in
|
||||
// the array, excluding the last N. The **guard** check allows it to work with
|
||||
// `_.map`.
|
||||
_.initial = function(array, n, guard) {
|
||||
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
|
||||
};
|
||||
|
||||
// Get the last element of an array. Passing **n** will return the last N
|
||||
// values in the array. The **guard** check allows it to work with `_.map`.
|
||||
_.last = function(array, n, guard) {
|
||||
if ((n != null) && !guard) {
|
||||
return slice.call(array, Math.max(array.length - n, 0));
|
||||
} else {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
};
|
||||
|
||||
// Returns everything but the first entry of the array. Aliased as `tail`.
|
||||
// Especially useful on the arguments object. Passing an **index** will return
|
||||
// the rest of the values in the array from that index onward. The **guard**
|
||||
// check allows it to work with `_.map`.
|
||||
_.rest = _.tail = function(array, index, guard) {
|
||||
return slice.call(array, (index == null) || guard ? 1 : index);
|
||||
};
|
||||
|
||||
// Trim out all falsy values from an array.
|
||||
_.compact = function(array) {
|
||||
return _.filter(array, function(value){ return !!value; });
|
||||
};
|
||||
|
||||
// Return a completely flattened version of an array.
|
||||
_.flatten = function(array, shallow) {
|
||||
return _.reduce(array, function(memo, value) {
|
||||
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
|
||||
memo[memo.length] = value;
|
||||
return memo;
|
||||
}, []);
|
||||
};
|
||||
|
||||
// Return a version of the array that does not contain the specified value(s).
|
||||
_.without = function(array) {
|
||||
return _.difference(array, slice.call(arguments, 1));
|
||||
};
|
||||
|
||||
// Produce a duplicate-free version of the array. If the array has already
|
||||
// been sorted, you have the option of using a faster algorithm.
|
||||
// Aliased as `unique`.
|
||||
_.uniq = _.unique = function(array, isSorted, iterator) {
|
||||
var initial = iterator ? _.map(array, iterator) : array;
|
||||
var result = [];
|
||||
_.reduce(initial, function(memo, el, i) {
|
||||
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
|
||||
memo[memo.length] = el;
|
||||
result[result.length] = array[i];
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
return result;
|
||||
};
|
||||
|
||||
// Produce an array that contains the union: each distinct element from all of
|
||||
// the passed-in arrays.
|
||||
_.union = function() {
|
||||
return _.uniq(_.flatten(arguments, true));
|
||||
};
|
||||
|
||||
// Produce an array that contains every item shared between all the
|
||||
// passed-in arrays. (Aliased as "intersect" for back-compat.)
|
||||
_.intersection = _.intersect = function(array) {
|
||||
var rest = slice.call(arguments, 1);
|
||||
return _.filter(_.uniq(array), function(item) {
|
||||
return _.every(rest, function(other) {
|
||||
return _.indexOf(other, item) >= 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Take the difference between one array and a number of other arrays.
|
||||
// Only the elements present in just the first array will remain.
|
||||
_.difference = function(array) {
|
||||
var rest = _.flatten(slice.call(arguments, 1));
|
||||
return _.filter(array, function(value){ return !_.include(rest, value); });
|
||||
};
|
||||
|
||||
// Zip together multiple lists into a single array -- elements that share
|
||||
// an index go together.
|
||||
_.zip = function() {
|
||||
var args = slice.call(arguments);
|
||||
var length = _.max(_.pluck(args, 'length'));
|
||||
var results = new Array(length);
|
||||
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
|
||||
return results;
|
||||
};
|
||||
|
||||
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
|
||||
// we need this function. Return the position of the first occurrence of an
|
||||
// item in an array, or -1 if the item is not included in the array.
|
||||
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
|
||||
// If the array is large and already in sort order, pass `true`
|
||||
// for **isSorted** to use binary search.
|
||||
_.indexOf = function(array, item, isSorted) {
|
||||
if (array == null) return -1;
|
||||
var i, l;
|
||||
if (isSorted) {
|
||||
i = _.sortedIndex(array, item);
|
||||
return array[i] === item ? i : -1;
|
||||
}
|
||||
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
|
||||
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
|
||||
_.lastIndexOf = function(array, item) {
|
||||
if (array == null) return -1;
|
||||
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
|
||||
var i = array.length;
|
||||
while (i--) if (i in array && array[i] === item) return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Generate an integer Array containing an arithmetic progression. A port of
|
||||
// the native Python `range()` function. See
|
||||
// [the Python documentation](http://docs.python.org/library/functions.html#range).
|
||||
_.range = function(start, stop, step) {
|
||||
if (arguments.length <= 1) {
|
||||
stop = start || 0;
|
||||
start = 0;
|
||||
}
|
||||
step = arguments[2] || 1;
|
||||
|
||||
var len = Math.max(Math.ceil((stop - start) / step), 0);
|
||||
var idx = 0;
|
||||
var range = new Array(len);
|
||||
|
||||
while(idx < len) {
|
||||
range[idx++] = start;
|
||||
start += step;
|
||||
}
|
||||
|
||||
return range;
|
||||
};
|
||||
|
||||
// Function (ahem) Functions
|
||||
// ------------------
|
||||
|
||||
// Reusable constructor function for prototype setting.
|
||||
var ctor = function(){};
|
||||
|
||||
// Create a function bound to a given object (assigning `this`, and arguments,
|
||||
// optionally). Binding with arguments is also known as `curry`.
|
||||
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
|
||||
// We check for `func.bind` first, to fail fast when `func` is undefined.
|
||||
_.bind = function bind(func, context) {
|
||||
var bound, args;
|
||||
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
|
||||
if (!_.isFunction(func)) throw new TypeError;
|
||||
args = slice.call(arguments, 2);
|
||||
return bound = function() {
|
||||
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
|
||||
ctor.prototype = func.prototype;
|
||||
var self = new ctor;
|
||||
var result = func.apply(self, args.concat(slice.call(arguments)));
|
||||
if (Object(result) === result) return result;
|
||||
return self;
|
||||
};
|
||||
};
|
||||
|
||||
// Bind all of an object's methods to that object. Useful for ensuring that
|
||||
// all callbacks defined on an object belong to it.
|
||||
_.bindAll = function(obj) {
|
||||
var funcs = slice.call(arguments, 1);
|
||||
if (funcs.length == 0) funcs = _.functions(obj);
|
||||
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Memoize an expensive function by storing its results.
|
||||
_.memoize = function(func, hasher) {
|
||||
var memo = {};
|
||||
hasher || (hasher = _.identity);
|
||||
return function() {
|
||||
var key = hasher.apply(this, arguments);
|
||||
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
|
||||
};
|
||||
};
|
||||
|
||||
// Delays a function for the given number of milliseconds, and then calls
|
||||
// it with the arguments supplied.
|
||||
_.delay = function(func, wait) {
|
||||
var args = slice.call(arguments, 2);
|
||||
return setTimeout(function(){ return func.apply(func, args); }, wait);
|
||||
};
|
||||
|
||||
// Defers a function, scheduling it to run after the current call stack has
|
||||
// cleared.
|
||||
_.defer = function(func) {
|
||||
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
|
||||
};
|
||||
|
||||
// Returns a function, that, when invoked, will only be triggered at most once
|
||||
// during a given window of time.
|
||||
_.throttle = function(func, wait) {
|
||||
var context, args, timeout, throttling, more;
|
||||
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
|
||||
return function() {
|
||||
context = this; args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (more) func.apply(context, args);
|
||||
whenDone();
|
||||
};
|
||||
if (!timeout) timeout = setTimeout(later, wait);
|
||||
if (throttling) {
|
||||
more = true;
|
||||
} else {
|
||||
func.apply(context, args);
|
||||
}
|
||||
whenDone();
|
||||
throttling = true;
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function, that, as long as it continues to be invoked, will not
|
||||
// be triggered. The function will be called after it stops being called for
|
||||
// N milliseconds.
|
||||
_.debounce = function(func, wait) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
func.apply(context, args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function that will be executed at most one time, no matter how
|
||||
// often you call it. Useful for lazy initialization.
|
||||
_.once = function(func) {
|
||||
var ran = false, memo;
|
||||
return function() {
|
||||
if (ran) return memo;
|
||||
ran = true;
|
||||
return memo = func.apply(this, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns the first function passed as an argument to the second,
|
||||
// allowing you to adjust arguments, run code before and after, and
|
||||
// conditionally execute the original function.
|
||||
_.wrap = function(func, wrapper) {
|
||||
return function() {
|
||||
var args = [func].concat(slice.call(arguments, 0));
|
||||
return wrapper.apply(this, args);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function that is the composition of a list of functions, each
|
||||
// consuming the return value of the function that follows.
|
||||
_.compose = function() {
|
||||
var funcs = arguments;
|
||||
return function() {
|
||||
var args = arguments;
|
||||
for (var i = funcs.length - 1; i >= 0; i--) {
|
||||
args = [funcs[i].apply(this, args)];
|
||||
}
|
||||
return args[0];
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function that will only be executed after being called N times.
|
||||
_.after = function(times, func) {
|
||||
if (times <= 0) return func();
|
||||
return function() {
|
||||
if (--times < 1) { return func.apply(this, arguments); }
|
||||
};
|
||||
};
|
||||
|
||||
// Object Functions
|
||||
// ----------------
|
||||
|
||||
// Retrieve the names of an object's properties.
|
||||
// Delegates to **ECMAScript 5**'s native `Object.keys`
|
||||
_.keys = nativeKeys || function(obj) {
|
||||
if (obj !== Object(obj)) throw new TypeError('Invalid object');
|
||||
var keys = [];
|
||||
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
|
||||
return keys;
|
||||
};
|
||||
|
||||
// Retrieve the values of an object's properties.
|
||||
_.values = function(obj) {
|
||||
return _.map(obj, _.identity);
|
||||
};
|
||||
|
||||
// Return a sorted list of the function names available on the object.
|
||||
// Aliased as `methods`
|
||||
_.functions = _.methods = function(obj) {
|
||||
var names = [];
|
||||
for (var key in obj) {
|
||||
if (_.isFunction(obj[key])) names.push(key);
|
||||
}
|
||||
return names.sort();
|
||||
};
|
||||
|
||||
// Extend a given object with all the properties in passed-in object(s).
|
||||
_.extend = function(obj) {
|
||||
each(slice.call(arguments, 1), function(source) {
|
||||
for (var prop in source) {
|
||||
obj[prop] = source[prop];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Fill in a given object with default properties.
|
||||
_.defaults = function(obj) {
|
||||
each(slice.call(arguments, 1), function(source) {
|
||||
for (var prop in source) {
|
||||
if (obj[prop] == null) obj[prop] = source[prop];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Create a (shallow-cloned) duplicate of an object.
|
||||
_.clone = function(obj) {
|
||||
if (!_.isObject(obj)) return obj;
|
||||
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
|
||||
};
|
||||
|
||||
// Invokes interceptor with the obj, and then returns obj.
|
||||
// The primary purpose of this method is to "tap into" a method chain, in
|
||||
// order to perform operations on intermediate results within the chain.
|
||||
_.tap = function(obj, interceptor) {
|
||||
interceptor(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Internal recursive comparison function.
|
||||
function eq(a, b, stack) {
|
||||
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
||||
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
|
||||
if (a === b) return a !== 0 || 1 / a == 1 / b;
|
||||
// A strict comparison is necessary because `null == undefined`.
|
||||
if (a == null || b == null) return a === b;
|
||||
// Unwrap any wrapped objects.
|
||||
if (a._chain) a = a._wrapped;
|
||||
if (b._chain) b = b._wrapped;
|
||||
// Invoke a custom `isEqual` method if one is provided.
|
||||
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
|
||||
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
|
||||
// Compare `[[Class]]` names.
|
||||
var className = toString.call(a);
|
||||
if (className != toString.call(b)) return false;
|
||||
switch (className) {
|
||||
// Strings, numbers, dates, and booleans are compared by value.
|
||||
case '[object String]':
|
||||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
||||
// equivalent to `new String("5")`.
|
||||
return a == String(b);
|
||||
case '[object Number]':
|
||||
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
|
||||
// other numeric values.
|
||||
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
|
||||
case '[object Date]':
|
||||
case '[object Boolean]':
|
||||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
||||
// millisecond representations. Note that invalid dates with millisecond representations
|
||||
// of `NaN` are not equivalent.
|
||||
return +a == +b;
|
||||
// RegExps are compared by their source patterns and flags.
|
||||
case '[object RegExp]':
|
||||
return a.source == b.source &&
|
||||
a.global == b.global &&
|
||||
a.multiline == b.multiline &&
|
||||
a.ignoreCase == b.ignoreCase;
|
||||
}
|
||||
if (typeof a != 'object' || typeof b != 'object') return false;
|
||||
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
||||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
||||
var length = stack.length;
|
||||
while (length--) {
|
||||
// Linear search. Performance is inversely proportional to the number of
|
||||
// unique nested structures.
|
||||
if (stack[length] == a) return true;
|
||||
}
|
||||
// Add the first object to the stack of traversed objects.
|
||||
stack.push(a);
|
||||
var size = 0, result = true;
|
||||
// Recursively compare objects and arrays.
|
||||
if (className == '[object Array]') {
|
||||
// Compare array lengths to determine if a deep comparison is necessary.
|
||||
size = a.length;
|
||||
result = size == b.length;
|
||||
if (result) {
|
||||
// Deep compare the contents, ignoring non-numeric properties.
|
||||
while (size--) {
|
||||
// Ensure commutative equality for sparse arrays.
|
||||
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Objects with different constructors are not equivalent.
|
||||
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
|
||||
// Deep compare objects.
|
||||
for (var key in a) {
|
||||
if (_.has(a, key)) {
|
||||
// Count the expected number of properties.
|
||||
size++;
|
||||
// Deep compare each member.
|
||||
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
|
||||
}
|
||||
}
|
||||
// Ensure that both objects contain the same number of properties.
|
||||
if (result) {
|
||||
for (key in b) {
|
||||
if (_.has(b, key) && !(size--)) break;
|
||||
}
|
||||
result = !size;
|
||||
}
|
||||
}
|
||||
// Remove the first object from the stack of traversed objects.
|
||||
stack.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Perform a deep comparison to check if two objects are equal.
|
||||
_.isEqual = function(a, b) {
|
||||
return eq(a, b, []);
|
||||
};
|
||||
|
||||
// Is a given array, string, or object empty?
|
||||
// An "empty" object has no enumerable own-properties.
|
||||
_.isEmpty = function(obj) {
|
||||
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
|
||||
for (var key in obj) if (_.has(obj, key)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Is a given value a DOM element?
|
||||
_.isElement = function(obj) {
|
||||
return !!(obj && obj.nodeType == 1);
|
||||
};
|
||||
|
||||
// Is a given value an array?
|
||||
// Delegates to ECMA5's native Array.isArray
|
||||
_.isArray = nativeIsArray || function(obj) {
|
||||
return toString.call(obj) == '[object Array]';
|
||||
};
|
||||
|
||||
// Is a given variable an object?
|
||||
_.isObject = function(obj) {
|
||||
return obj === Object(obj);
|
||||
};
|
||||
|
||||
// Is a given variable an arguments object?
|
||||
_.isArguments = function(obj) {
|
||||
return toString.call(obj) == '[object Arguments]';
|
||||
};
|
||||
if (!_.isArguments(arguments)) {
|
||||
_.isArguments = function(obj) {
|
||||
return !!(obj && _.has(obj, 'callee'));
|
||||
};
|
||||
}
|
||||
|
||||
// Is a given value a function?
|
||||
_.isFunction = function(obj) {
|
||||
return toString.call(obj) == '[object Function]';
|
||||
};
|
||||
|
||||
// Is a given value a string?
|
||||
_.isString = function(obj) {
|
||||
return toString.call(obj) == '[object String]';
|
||||
};
|
||||
|
||||
// Is a given value a number?
|
||||
_.isNumber = function(obj) {
|
||||
return toString.call(obj) == '[object Number]';
|
||||
};
|
||||
|
||||
// Is the given value `NaN`?
|
||||
_.isNaN = function(obj) {
|
||||
// `NaN` is the only value for which `===` is not reflexive.
|
||||
return obj !== obj;
|
||||
};
|
||||
|
||||
// Is a given value a boolean?
|
||||
_.isBoolean = function(obj) {
|
||||
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
|
||||
};
|
||||
|
||||
// Is a given value a date?
|
||||
_.isDate = function(obj) {
|
||||
return toString.call(obj) == '[object Date]';
|
||||
};
|
||||
|
||||
// Is the given value a regular expression?
|
||||
_.isRegExp = function(obj) {
|
||||
return toString.call(obj) == '[object RegExp]';
|
||||
};
|
||||
|
||||
// Is a given value equal to null?
|
||||
_.isNull = function(obj) {
|
||||
return obj === null;
|
||||
};
|
||||
|
||||
// Is a given variable undefined?
|
||||
_.isUndefined = function(obj) {
|
||||
return obj === void 0;
|
||||
};
|
||||
|
||||
// Has own property?
|
||||
_.has = function(obj, key) {
|
||||
return hasOwnProperty.call(obj, key);
|
||||
};
|
||||
|
||||
// Utility Functions
|
||||
// -----------------
|
||||
|
||||
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
|
||||
// previous owner. Returns a reference to the Underscore object.
|
||||
_.noConflict = function() {
|
||||
root._ = previousUnderscore;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Keep the identity function around for default iterators.
|
||||
_.identity = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
// Run a function **n** times.
|
||||
_.times = function (n, iterator, context) {
|
||||
for (var i = 0; i < n; i++) iterator.call(context, i);
|
||||
};
|
||||
|
||||
// Escape a string for HTML interpolation.
|
||||
_.escape = function(string) {
|
||||
return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
|
||||
};
|
||||
|
||||
// Add your own custom functions to the Underscore object, ensuring that
|
||||
// they're correctly added to the OOP wrapper as well.
|
||||
_.mixin = function(obj) {
|
||||
each(_.functions(obj), function(name){
|
||||
addToWrapper(name, _[name] = obj[name]);
|
||||
});
|
||||
};
|
||||
|
||||
// Generate a unique integer id (unique within the entire client session).
|
||||
// Useful for temporary DOM ids.
|
||||
var idCounter = 0;
|
||||
_.uniqueId = function(prefix) {
|
||||
var id = idCounter++;
|
||||
return prefix ? prefix + id : id;
|
||||
};
|
||||
|
||||
// By default, Underscore uses ERB-style template delimiters, change the
|
||||
// following template settings to use alternative delimiters.
|
||||
_.templateSettings = {
|
||||
evaluate : /<%([\s\S]+?)%>/g,
|
||||
interpolate : /<%=([\s\S]+?)%>/g,
|
||||
escape : /<%-([\s\S]+?)%>/g
|
||||
};
|
||||
|
||||
// When customizing `templateSettings`, if you don't want to define an
|
||||
// interpolation, evaluation or escaping regex, we need one that is
|
||||
// guaranteed not to match.
|
||||
var noMatch = /.^/;
|
||||
|
||||
// Within an interpolation, evaluation, or escaping, remove HTML escaping
|
||||
// that had been previously added.
|
||||
var unescape = function(code) {
|
||||
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
|
||||
};
|
||||
|
||||
// JavaScript micro-templating, similar to John Resig's implementation.
|
||||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
||||
// and correctly escapes quotes within interpolated code.
|
||||
_.template = function(str, data) {
|
||||
var c = _.templateSettings;
|
||||
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
|
||||
'with(obj||{}){__p.push(\'' +
|
||||
str.replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(c.escape || noMatch, function(match, code) {
|
||||
return "',_.escape(" + unescape(code) + "),'";
|
||||
})
|
||||
.replace(c.interpolate || noMatch, function(match, code) {
|
||||
return "'," + unescape(code) + ",'";
|
||||
})
|
||||
.replace(c.evaluate || noMatch, function(match, code) {
|
||||
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
|
||||
})
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\t/g, '\\t')
|
||||
+ "');}return __p.join('');";
|
||||
var func = new Function('obj', '_', tmpl);
|
||||
if (data) return func(data, _);
|
||||
return function(data) {
|
||||
return func.call(this, data, _);
|
||||
};
|
||||
};
|
||||
|
||||
// Add a "chain" function, which will delegate to the wrapper.
|
||||
_.chain = function(obj) {
|
||||
return _(obj).chain();
|
||||
};
|
||||
|
||||
// The OOP Wrapper
|
||||
// ---------------
|
||||
|
||||
// If Underscore is called as a function, it returns a wrapped object that
|
||||
// can be used OO-style. This wrapper holds altered versions of all the
|
||||
// underscore functions. Wrapped objects may be chained.
|
||||
var wrapper = function(obj) { this._wrapped = obj; };
|
||||
|
||||
// Expose `wrapper.prototype` as `_.prototype`
|
||||
_.prototype = wrapper.prototype;
|
||||
|
||||
// Helper function to continue chaining intermediate results.
|
||||
var result = function(obj, chain) {
|
||||
return chain ? _(obj).chain() : obj;
|
||||
};
|
||||
|
||||
// A method to easily add functions to the OOP wrapper.
|
||||
var addToWrapper = function(name, func) {
|
||||
wrapper.prototype[name] = function() {
|
||||
var args = slice.call(arguments);
|
||||
unshift.call(args, this._wrapped);
|
||||
return result(func.apply(_, args), this._chain);
|
||||
};
|
||||
};
|
||||
|
||||
// Add all of the Underscore functions to the wrapper object.
|
||||
_.mixin(_);
|
||||
|
||||
// Add all mutator Array functions to the wrapper.
|
||||
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
||||
var method = ArrayProto[name];
|
||||
wrapper.prototype[name] = function() {
|
||||
var wrapped = this._wrapped;
|
||||
method.apply(wrapped, arguments);
|
||||
var length = wrapped.length;
|
||||
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
|
||||
return result(wrapped, this._chain);
|
||||
};
|
||||
});
|
||||
|
||||
// Add all accessor Array functions to the wrapper.
|
||||
each(['concat', 'join', 'slice'], function(name) {
|
||||
var method = ArrayProto[name];
|
||||
wrapper.prototype[name] = function() {
|
||||
return result(method.apply(this._wrapped, arguments), this._chain);
|
||||
};
|
||||
});
|
||||
|
||||
// Start chaining a wrapped Underscore object.
|
||||
wrapper.prototype.chain = function() {
|
||||
this._chain = true;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Extracts the result from a wrapped and chained object.
|
||||
wrapper.prototype.value = function() {
|
||||
return this._wrapped;
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
91
static/js/libs/bootstrap/alert.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/* ==========================================================
|
||||
* bootstrap-alert.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#alerts
|
||||
* ==========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* ALERT CLASS DEFINITION
|
||||
* ====================== */
|
||||
|
||||
var dismiss = '[data-dismiss="alert"]'
|
||||
, Alert = function ( el ) {
|
||||
$(el).on('click', dismiss, this.close)
|
||||
}
|
||||
|
||||
Alert.prototype = {
|
||||
|
||||
constructor: Alert
|
||||
|
||||
, close: function ( e ) {
|
||||
var $this = $(this)
|
||||
, selector = $this.attr('data-target')
|
||||
, $parent
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
}
|
||||
|
||||
$parent = $(selector)
|
||||
$parent.trigger('close')
|
||||
|
||||
e && e.preventDefault()
|
||||
|
||||
$parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
|
||||
|
||||
$parent.removeClass('in')
|
||||
|
||||
function removeElement() {
|
||||
$parent.remove()
|
||||
$parent.trigger('closed')
|
||||
}
|
||||
|
||||
$.support.transition && $parent.hasClass('fade') ?
|
||||
$parent.on($.support.transition.end, removeElement) :
|
||||
removeElement()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ALERT PLUGIN DEFINITION
|
||||
* ======================= */
|
||||
|
||||
$.fn.alert = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('alert')
|
||||
if (!data) $this.data('alert', (data = new Alert(this)))
|
||||
if (typeof option == 'string') data[option].call($this)
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.alert.Constructor = Alert
|
||||
|
||||
|
||||
/* ALERT DATA-API
|
||||
* ============== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
1720
static/js/libs/bootstrap/bootstrap.js
vendored
Normal file
1
static/js/libs/bootstrap/bootstrap.min.js
vendored
Normal file
98
static/js/libs/bootstrap/button.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/* ============================================================
|
||||
* bootstrap-button.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#buttons
|
||||
* ============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* BUTTON PUBLIC CLASS DEFINITION
|
||||
* ============================== */
|
||||
|
||||
var Button = function ( element, options ) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, $.fn.button.defaults, options)
|
||||
}
|
||||
|
||||
Button.prototype = {
|
||||
|
||||
constructor: Button
|
||||
|
||||
, setState: function ( state ) {
|
||||
var d = 'disabled'
|
||||
, $el = this.$element
|
||||
, data = $el.data()
|
||||
, val = $el.is('input') ? 'val' : 'html'
|
||||
|
||||
state = state + 'Text'
|
||||
data.resetText || $el.data('resetText', $el[val]())
|
||||
|
||||
$el[val](data[state] || this.options[state])
|
||||
|
||||
// push to event loop to allow forms to submit
|
||||
setTimeout(function () {
|
||||
state == 'loadingText' ?
|
||||
$el.addClass(d).attr(d, d) :
|
||||
$el.removeClass(d).removeAttr(d)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
, toggle: function () {
|
||||
var $parent = this.$element.parent('[data-toggle="buttons-radio"]')
|
||||
|
||||
$parent && $parent
|
||||
.find('.active')
|
||||
.removeClass('active')
|
||||
|
||||
this.$element.toggleClass('active')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* BUTTON PLUGIN DEFINITION
|
||||
* ======================== */
|
||||
|
||||
$.fn.button = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('button')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('button', (data = new Button(this, options)))
|
||||
if (option == 'toggle') data.toggle()
|
||||
else if (option) data.setState(option)
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.button.defaults = {
|
||||
loadingText: 'loading...'
|
||||
}
|
||||
|
||||
$.fn.button.Constructor = Button
|
||||
|
||||
|
||||
/* BUTTON DATA-API
|
||||
* =============== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
|
||||
$(e.target).button('toggle')
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
154
static/js/libs/bootstrap/carousel.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/* ==========================================================
|
||||
* bootstrap-carousel.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#carousel
|
||||
* ==========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* CAROUSEL CLASS DEFINITION
|
||||
* ========================= */
|
||||
|
||||
var Carousel = function (element, options) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, $.fn.carousel.defaults, options)
|
||||
this.options.slide && this.slide(this.options.slide)
|
||||
}
|
||||
|
||||
Carousel.prototype = {
|
||||
|
||||
cycle: function () {
|
||||
this.interval = setInterval($.proxy(this.next, this), this.options.interval)
|
||||
return this
|
||||
}
|
||||
|
||||
, to: function (pos) {
|
||||
var $active = this.$element.find('.active')
|
||||
, children = $active.parent().children()
|
||||
, activePos = children.index($active)
|
||||
, that = this
|
||||
|
||||
if (pos > (children.length - 1) || pos < 0) return
|
||||
|
||||
if (this.sliding) {
|
||||
return this.$element.one('slid', function () {
|
||||
that.to(pos)
|
||||
})
|
||||
}
|
||||
|
||||
if (activePos == pos) {
|
||||
return this.pause().cycle()
|
||||
}
|
||||
|
||||
return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
|
||||
}
|
||||
|
||||
, pause: function () {
|
||||
clearInterval(this.interval)
|
||||
return this
|
||||
}
|
||||
|
||||
, next: function () {
|
||||
if (this.sliding) return
|
||||
return this.slide('next')
|
||||
}
|
||||
|
||||
, prev: function () {
|
||||
if (this.sliding) return
|
||||
return this.slide('prev')
|
||||
}
|
||||
|
||||
, slide: function (type, next) {
|
||||
var $active = this.$element.find('.active')
|
||||
, $next = next || $active[type]()
|
||||
, isCycling = this.interval
|
||||
, direction = type == 'next' ? 'left' : 'right'
|
||||
, fallback = type == 'next' ? 'first' : 'last'
|
||||
, that = this
|
||||
|
||||
this.sliding = true
|
||||
|
||||
isCycling && this.pause()
|
||||
|
||||
$next = $next.length ? $next : this.$element.find('.item')[fallback]()
|
||||
|
||||
if (!$.support.transition && this.$element.hasClass('slide')) {
|
||||
this.$element.trigger('slide')
|
||||
$active.removeClass('active')
|
||||
$next.addClass('active')
|
||||
this.sliding = false
|
||||
this.$element.trigger('slid')
|
||||
} else {
|
||||
$next.addClass(type)
|
||||
$next[0].offsetWidth // force reflow
|
||||
$active.addClass(direction)
|
||||
$next.addClass(direction)
|
||||
this.$element.trigger('slide')
|
||||
this.$element.one($.support.transition.end, function () {
|
||||
$next.removeClass([type, direction].join(' ')).addClass('active')
|
||||
$active.removeClass(['active', direction].join(' '))
|
||||
that.sliding = false
|
||||
setTimeout(function () { that.$element.trigger('slid') }, 0)
|
||||
})
|
||||
}
|
||||
|
||||
isCycling && this.cycle()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* CAROUSEL PLUGIN DEFINITION
|
||||
* ========================== */
|
||||
|
||||
$.fn.carousel = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('carousel')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('carousel', (data = new Carousel(this, options)))
|
||||
if (typeof option == 'number') data.to(option)
|
||||
else if (typeof option == 'string' || (option = options.slide)) data[option]()
|
||||
else data.cycle()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.carousel.defaults = {
|
||||
interval: 5000
|
||||
}
|
||||
|
||||
$.fn.carousel.Constructor = Carousel
|
||||
|
||||
|
||||
/* CAROUSEL DATA-API
|
||||
* ================= */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
|
||||
var $this = $(this), href
|
||||
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
, options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
|
||||
$target.carousel(options)
|
||||
e.preventDefault()
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
136
static/js/libs/bootstrap/collapse.js
Normal file
@@ -0,0 +1,136 @@
|
||||
/* =============================================================
|
||||
* bootstrap-collapse.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#collapse
|
||||
* =============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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 Collapse = function ( element, options ) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, $.fn.collapse.defaults, options)
|
||||
|
||||
if (this.options["parent"]) {
|
||||
this.$parent = $(this.options["parent"])
|
||||
}
|
||||
|
||||
this.options.toggle && this.toggle()
|
||||
}
|
||||
|
||||
Collapse.prototype = {
|
||||
|
||||
constructor: Collapse
|
||||
|
||||
, dimension: function () {
|
||||
var hasWidth = this.$element.hasClass('width')
|
||||
return hasWidth ? 'width' : 'height'
|
||||
}
|
||||
|
||||
, show: function () {
|
||||
var dimension = this.dimension()
|
||||
, scroll = $.camelCase(['scroll', dimension].join('-'))
|
||||
, actives = this.$parent && this.$parent.find('.in')
|
||||
, hasData
|
||||
|
||||
if (actives && actives.length) {
|
||||
hasData = actives.data('collapse')
|
||||
actives.collapse('hide')
|
||||
hasData || actives.data('collapse', null)
|
||||
}
|
||||
|
||||
this.$element[dimension](0)
|
||||
this.transition('addClass', 'show', 'shown')
|
||||
this.$element[dimension](this.$element[0][scroll])
|
||||
|
||||
}
|
||||
|
||||
, hide: function () {
|
||||
var dimension = this.dimension()
|
||||
this.reset(this.$element[dimension]())
|
||||
this.transition('removeClass', 'hide', 'hidden')
|
||||
this.$element[dimension](0)
|
||||
}
|
||||
|
||||
, reset: function ( size ) {
|
||||
var dimension = this.dimension()
|
||||
|
||||
this.$element
|
||||
.removeClass('collapse')
|
||||
[dimension](size || 'auto')
|
||||
[0].offsetWidth
|
||||
|
||||
this.$element.addClass('collapse')
|
||||
}
|
||||
|
||||
, transition: function ( method, startEvent, completeEvent ) {
|
||||
var that = this
|
||||
, complete = function () {
|
||||
if (startEvent == 'show') that.reset()
|
||||
that.$element.trigger(completeEvent)
|
||||
}
|
||||
|
||||
this.$element
|
||||
.trigger(startEvent)
|
||||
[method]('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('collapse') ?
|
||||
this.$element.one($.support.transition.end, complete) :
|
||||
complete()
|
||||
}
|
||||
|
||||
, toggle: function () {
|
||||
this[this.$element.hasClass('in') ? 'hide' : 'show']()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* COLLAPSIBLE PLUGIN DEFINITION
|
||||
* ============================== */
|
||||
|
||||
$.fn.collapse = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('collapse')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('collapse', (data = new Collapse(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.collapse.defaults = {
|
||||
toggle: true
|
||||
}
|
||||
|
||||
$.fn.collapse.Constructor = Collapse
|
||||
|
||||
|
||||
/* COLLAPSIBLE DATA-API
|
||||
* ==================== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) {
|
||||
var $this = $(this), href
|
||||
, target = $this.attr('data-target')
|
||||
|| e.preventDefault()
|
||||
|| (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
|
||||
, option = $(target).data('collapse') ? 'toggle' : $this.data()
|
||||
$(target).collapse(option)
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
92
static/js/libs/bootstrap/dropdown.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/* ============================================================
|
||||
* bootstrap-dropdown.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
|
||||
* ============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* DROPDOWN CLASS DEFINITION
|
||||
* ========================= */
|
||||
|
||||
var toggle = '[data-toggle="dropdown"]'
|
||||
, Dropdown = function ( element ) {
|
||||
var $el = $(element).on('click.dropdown.data-api', this.toggle)
|
||||
$('html').on('click.dropdown.data-api', function () {
|
||||
$el.parent().removeClass('open')
|
||||
})
|
||||
}
|
||||
|
||||
Dropdown.prototype = {
|
||||
|
||||
constructor: Dropdown
|
||||
|
||||
, toggle: function ( e ) {
|
||||
var $this = $(this)
|
||||
, selector = $this.attr('data-target')
|
||||
, $parent
|
||||
, isActive
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
}
|
||||
|
||||
$parent = $(selector)
|
||||
$parent.length || ($parent = $this.parent())
|
||||
|
||||
isActive = $parent.hasClass('open')
|
||||
|
||||
clearMenus()
|
||||
!isActive && $parent.toggleClass('open')
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function clearMenus() {
|
||||
$(toggle).parent().removeClass('open')
|
||||
}
|
||||
|
||||
|
||||
/* DROPDOWN PLUGIN DEFINITION
|
||||
* ========================== */
|
||||
|
||||
$.fn.dropdown = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('dropdown')
|
||||
if (!data) $this.data('dropdown', (data = new Dropdown(this)))
|
||||
if (typeof option == 'string') data[option].call($this)
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.dropdown.Constructor = Dropdown
|
||||
|
||||
|
||||
/* APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
* =================================== */
|
||||
|
||||
$(function () {
|
||||
$('html').on('click.dropdown.data-api', clearMenus)
|
||||
$('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
209
static/js/libs/bootstrap/modal.js
Normal file
@@ -0,0 +1,209 @@
|
||||
/* =========================================================
|
||||
* bootstrap-modal.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#modals
|
||||
* =========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* MODAL CLASS DEFINITION
|
||||
* ====================== */
|
||||
|
||||
var Modal = function ( content, options ) {
|
||||
this.options = $.extend({}, $.fn.modal.defaults, options)
|
||||
this.$element = $(content)
|
||||
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
|
||||
}
|
||||
|
||||
Modal.prototype = {
|
||||
|
||||
constructor: Modal
|
||||
|
||||
, toggle: function () {
|
||||
return this[!this.isShown ? 'show' : 'hide']()
|
||||
}
|
||||
|
||||
, show: function () {
|
||||
var that = this
|
||||
|
||||
if (this.isShown) return
|
||||
|
||||
$('body').addClass('modal-open')
|
||||
|
||||
this.isShown = true
|
||||
this.$element.trigger('show')
|
||||
|
||||
escape.call(this)
|
||||
backdrop.call(this, function () {
|
||||
var transition = $.support.transition && that.$element.hasClass('fade')
|
||||
|
||||
!that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position
|
||||
|
||||
that.$element
|
||||
.show()
|
||||
|
||||
if (transition) {
|
||||
that.$element[0].offsetWidth // force reflow
|
||||
}
|
||||
|
||||
that.$element.addClass('in')
|
||||
|
||||
transition ?
|
||||
that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
|
||||
that.$element.trigger('shown')
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
, hide: function ( e ) {
|
||||
e && e.preventDefault()
|
||||
|
||||
if (!this.isShown) return
|
||||
|
||||
var that = this
|
||||
this.isShown = false
|
||||
|
||||
$('body').removeClass('modal-open')
|
||||
|
||||
escape.call(this)
|
||||
|
||||
this.$element
|
||||
.trigger('hide')
|
||||
.removeClass('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade') ?
|
||||
hideWithTransition.call(this) :
|
||||
hideModal.call(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* MODAL PRIVATE METHODS
|
||||
* ===================== */
|
||||
|
||||
function hideWithTransition() {
|
||||
var that = this
|
||||
, timeout = setTimeout(function () {
|
||||
that.$element.off($.support.transition.end)
|
||||
hideModal.call(that)
|
||||
}, 500)
|
||||
|
||||
this.$element.one($.support.transition.end, function () {
|
||||
clearTimeout(timeout)
|
||||
hideModal.call(that)
|
||||
})
|
||||
}
|
||||
|
||||
function hideModal( that ) {
|
||||
this.$element
|
||||
.hide()
|
||||
.trigger('hidden')
|
||||
|
||||
backdrop.call(this)
|
||||
}
|
||||
|
||||
function backdrop( callback ) {
|
||||
var that = this
|
||||
, animate = this.$element.hasClass('fade') ? 'fade' : ''
|
||||
|
||||
if (this.isShown && this.options.backdrop) {
|
||||
var doAnimate = $.support.transition && animate
|
||||
|
||||
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
|
||||
.appendTo(document.body)
|
||||
|
||||
if (this.options.backdrop != 'static') {
|
||||
this.$backdrop.click($.proxy(this.hide, this))
|
||||
}
|
||||
|
||||
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
|
||||
|
||||
this.$backdrop.addClass('in')
|
||||
|
||||
doAnimate ?
|
||||
this.$backdrop.one($.support.transition.end, callback) :
|
||||
callback()
|
||||
|
||||
} else if (!this.isShown && this.$backdrop) {
|
||||
this.$backdrop.removeClass('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade')?
|
||||
this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) :
|
||||
removeBackdrop.call(this)
|
||||
|
||||
} else if (callback) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
function removeBackdrop() {
|
||||
this.$backdrop.remove()
|
||||
this.$backdrop = null
|
||||
}
|
||||
|
||||
function escape() {
|
||||
var that = this
|
||||
if (this.isShown && this.options.keyboard) {
|
||||
$(document).on('keyup.dismiss.modal', function ( e ) {
|
||||
e.which == 27 && that.hide()
|
||||
})
|
||||
} else if (!this.isShown) {
|
||||
$(document).off('keyup.dismiss.modal')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* MODAL PLUGIN DEFINITION
|
||||
* ======================= */
|
||||
|
||||
$.fn.modal = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('modal')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('modal', (data = new Modal(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
else data.show()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.modal.defaults = {
|
||||
backdrop: true
|
||||
, keyboard: true
|
||||
}
|
||||
|
||||
$.fn.modal.Constructor = Modal
|
||||
|
||||
|
||||
/* MODAL DATA-API
|
||||
* ============== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
|
||||
var $this = $(this), href
|
||||
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
, option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data())
|
||||
|
||||
e.preventDefault()
|
||||
$target.modal(option)
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
95
static/js/libs/bootstrap/popover.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/* ===========================================================
|
||||
* bootstrap-popover.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#popovers
|
||||
* ===========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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 Popover = function ( element, options ) {
|
||||
this.init('popover', element, options)
|
||||
}
|
||||
|
||||
/* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
|
||||
========================================== */
|
||||
|
||||
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
|
||||
|
||||
constructor: Popover
|
||||
|
||||
, setContent: function () {
|
||||
var $tip = this.tip()
|
||||
, title = this.getTitle()
|
||||
, content = this.getContent()
|
||||
|
||||
$tip.find('.popover-title')[ $.type(title) == 'object' ? 'append' : 'html' ](title)
|
||||
$tip.find('.popover-content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content)
|
||||
|
||||
$tip.removeClass('fade top bottom left right in')
|
||||
}
|
||||
|
||||
, hasContent: function () {
|
||||
return this.getTitle() || this.getContent()
|
||||
}
|
||||
|
||||
, getContent: function () {
|
||||
var content
|
||||
, $e = this.$element
|
||||
, o = this.options
|
||||
|
||||
content = $e.attr('data-content')
|
||||
|| (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
|
||||
|
||||
content = content.toString().replace(/(^\s*|\s*$)/, "")
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
, tip: function() {
|
||||
if (!this.$tip) {
|
||||
this.$tip = $(this.options.template)
|
||||
}
|
||||
return this.$tip
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
/* POPOVER PLUGIN DEFINITION
|
||||
* ======================= */
|
||||
|
||||
$.fn.popover = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('popover')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('popover', (data = new Popover(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.popover.Constructor = Popover
|
||||
|
||||
$.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
|
||||
placement: 'right'
|
||||
, content: ''
|
||||
, template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
125
static/js/libs/bootstrap/scrollspy.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/* =============================================================
|
||||
* bootstrap-scrollspy.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#scrollspy
|
||||
* =============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* SCROLLSPY CLASS DEFINITION
|
||||
* ========================== */
|
||||
|
||||
function ScrollSpy( element, options) {
|
||||
var process = $.proxy(this.process, this)
|
||||
, $element = $(element).is('body') ? $(window) : $(element)
|
||||
, href
|
||||
this.options = $.extend({}, $.fn.scrollspy.defaults, options)
|
||||
this.$scrollElement = $element.on('scroll.scroll.data-api', process)
|
||||
this.selector = (this.options.target
|
||||
|| ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
|| '') + ' .nav li > a'
|
||||
this.$body = $('body').on('click.scroll.data-api', this.selector, process)
|
||||
this.refresh()
|
||||
this.process()
|
||||
}
|
||||
|
||||
ScrollSpy.prototype = {
|
||||
|
||||
constructor: ScrollSpy
|
||||
|
||||
, refresh: function () {
|
||||
this.targets = this.$body
|
||||
.find(this.selector)
|
||||
.map(function () {
|
||||
var href = $(this).attr('href')
|
||||
return /^#\w/.test(href) && $(href).length ? href : null
|
||||
})
|
||||
|
||||
this.offsets = $.map(this.targets, function (id) {
|
||||
return $(id).position().top
|
||||
})
|
||||
}
|
||||
|
||||
, process: function () {
|
||||
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
|
||||
, offsets = this.offsets
|
||||
, targets = this.targets
|
||||
, activeTarget = this.activeTarget
|
||||
, i
|
||||
|
||||
for (i = offsets.length; i--;) {
|
||||
activeTarget != targets[i]
|
||||
&& scrollTop >= offsets[i]
|
||||
&& (!offsets[i + 1] || scrollTop <= offsets[i + 1])
|
||||
&& this.activate( targets[i] )
|
||||
}
|
||||
}
|
||||
|
||||
, activate: function (target) {
|
||||
var active
|
||||
|
||||
this.activeTarget = target
|
||||
|
||||
this.$body
|
||||
.find(this.selector).parent('.active')
|
||||
.removeClass('active')
|
||||
|
||||
active = this.$body
|
||||
.find(this.selector + '[href="' + target + '"]')
|
||||
.parent('li')
|
||||
.addClass('active')
|
||||
|
||||
if ( active.parent('.dropdown-menu') ) {
|
||||
active.closest('li.dropdown').addClass('active')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* SCROLLSPY PLUGIN DEFINITION
|
||||
* =========================== */
|
||||
|
||||
$.fn.scrollspy = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('scrollspy')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.scrollspy.Constructor = ScrollSpy
|
||||
|
||||
$.fn.scrollspy.defaults = {
|
||||
offset: 10
|
||||
}
|
||||
|
||||
|
||||
/* SCROLLSPY DATA-API
|
||||
* ================== */
|
||||
|
||||
$(function () {
|
||||
$('[data-spy="scroll"]').each(function () {
|
||||
var $spy = $(this)
|
||||
$spy.scrollspy($spy.data())
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
130
static/js/libs/bootstrap/tab.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/* ========================================================
|
||||
* bootstrap-tab.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#tabs
|
||||
* ========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* TAB CLASS DEFINITION
|
||||
* ==================== */
|
||||
|
||||
var Tab = function ( element ) {
|
||||
this.element = $(element)
|
||||
}
|
||||
|
||||
Tab.prototype = {
|
||||
|
||||
constructor: Tab
|
||||
|
||||
, show: function () {
|
||||
var $this = this.element
|
||||
, $ul = $this.closest('ul:not(.dropdown-menu)')
|
||||
, selector = $this.attr('data-target')
|
||||
, previous
|
||||
, $target
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
}
|
||||
|
||||
if ( $this.parent('li').hasClass('active') ) return
|
||||
|
||||
previous = $ul.find('.active a').last()[0]
|
||||
|
||||
$this.trigger({
|
||||
type: 'show'
|
||||
, relatedTarget: previous
|
||||
})
|
||||
|
||||
$target = $(selector)
|
||||
|
||||
this.activate($this.parent('li'), $ul)
|
||||
this.activate($target, $target.parent(), function () {
|
||||
$this.trigger({
|
||||
type: 'shown'
|
||||
, relatedTarget: previous
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
, activate: function ( element, container, callback) {
|
||||
var $active = container.find('> .active')
|
||||
, transition = callback
|
||||
&& $.support.transition
|
||||
&& $active.hasClass('fade')
|
||||
|
||||
function next() {
|
||||
$active
|
||||
.removeClass('active')
|
||||
.find('> .dropdown-menu > .active')
|
||||
.removeClass('active')
|
||||
|
||||
element.addClass('active')
|
||||
|
||||
if (transition) {
|
||||
element[0].offsetWidth // reflow for transition
|
||||
element.addClass('in')
|
||||
} else {
|
||||
element.removeClass('fade')
|
||||
}
|
||||
|
||||
if ( element.parent('.dropdown-menu') ) {
|
||||
element.closest('li.dropdown').addClass('active')
|
||||
}
|
||||
|
||||
callback && callback()
|
||||
}
|
||||
|
||||
transition ?
|
||||
$active.one($.support.transition.end, next) :
|
||||
next()
|
||||
|
||||
$active.removeClass('in')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* TAB PLUGIN DEFINITION
|
||||
* ===================== */
|
||||
|
||||
$.fn.tab = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('tab')
|
||||
if (!data) $this.data('tab', (data = new Tab(this)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.tab.Constructor = Tab
|
||||
|
||||
|
||||
/* TAB DATA-API
|
||||
* ============ */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
|
||||
e.preventDefault()
|
||||
$(this).tab('show')
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||