Initial commit after change to backbone/SPA

This commit is contained in:
fergal.moran
2012-08-09 11:02:18 +01:00
commit e49f36eb14
166 changed files with 23705 additions and 0 deletions

1
README Normal file
View File

@@ -0,0 +1 @@
Deep 'n Corky like..

1
core/__init__.py Normal file
View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

26
core/decorators.py Normal file
View 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
View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

8
core/tasks/waveform.py Normal file
View 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
View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

5
core/utils/file.py Normal file
View 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
View 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
View 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
View 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
View File

161
dss/settings.py Normal file
View 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
View 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
View 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
View 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
View 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
View File

16
spa/admin.py Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

View 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

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

View 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

View 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
View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

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

View 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
View 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
View 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))

View File

@@ -0,0 +1 @@
__author__ = 'fergalm'

View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
from core.decorators import render_template
@render_template
def app(request):
return "inc/app.html"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

808
static/css/bootstrap-responsive.css vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

52
static/css/colorbox.css Normal file
View 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
View 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
View 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
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

BIN
static/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
static/img/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

BIN
static/img/noise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/img/pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
static/img/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/img/playhead.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

BIN
static/img/sheen3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
static/img/site-logo-gr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

BIN
static/img/slide-nav.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

206
static/img/variables.less Normal file
View 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
View 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);

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

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

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

View File

@@ -0,0 +1,7 @@
var ReleaseAudio = TastypieModel.extend({
urlRoot:window.appSettings.urlRoot + "release_audio/"
});
var ReleaseAudioCollection = TastypieCollection.extend({
model:ReleaseAudio
});

View File

@@ -0,0 +1,4 @@
if (!window.appSettings){
window.appSettings = {};
appSettings.urlRoot = "/api/v1/";
}

38
static/js/app/site.js Normal file
View 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
View 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;
}
});

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

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

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

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

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

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

View 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
View 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 = {
"&":"&amp;",
"<":"&lt;",
">":"&gt;",
'"':'&quot;',
"'":'&#39;'
};
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 &nbsp;
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);
}
}
})();
})();

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

File diff suppressed because it is too large Load Diff

View 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// 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);

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View 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 )

View 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 )

View 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 )

View 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 )

View 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 )

View 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 )

View 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 )

View 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 )

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