Merge remote-tracking branch 'origin/master' into dormsbee/multicourse
Conflicts: common/lib/xmodule/xmodule/capa_module.py common/lib/xmodule/xmodule/modulestore/xml.py lms/djangoapps/courseware/views.py lms/templates/index.html lms/templates/info.html lms/templates/main.html lms/templates/navigation.html requirements.txt
15
Gemfile.lock
@@ -1,15 +0,0 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
bourbon (1.3.6)
|
||||
sass (>= 3.1)
|
||||
rake (0.9.2.2)
|
||||
sass (3.1.15)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bourbon (~> 1.3.6)
|
||||
rake
|
||||
sass (= 3.1.15)
|
||||
@@ -0,0 +1,29 @@
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def import_from_xml(org, course, data_dir):
|
||||
"""
|
||||
Import the specified xml data_dir into the django defined modulestore,
|
||||
using org and course as the location org and course.
|
||||
"""
|
||||
module_store = XMLModuleStore(org, course, data_dir, 'xmodule.raw_module.RawDescriptor', eager=True)
|
||||
for module in module_store.modules.itervalues():
|
||||
|
||||
# TODO (cpennington): This forces import to overrite the same items.
|
||||
# This should in the future create new revisions of the items on import
|
||||
try:
|
||||
modulestore().create_item(module.location)
|
||||
except:
|
||||
log.exception('Item already exists at %s' % module.location.url())
|
||||
pass
|
||||
if 'data' in module.definition:
|
||||
modulestore().update_item(module.location, module.definition['data'])
|
||||
if 'children' in module.definition:
|
||||
modulestore().update_children(module.location, module.definition['children'])
|
||||
modulestore().update_metadata(module.location, dict(module.metadata))
|
||||
|
||||
return module_store.course
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
###
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
from contentstore import import_from_xml
|
||||
|
||||
unnamed_modules = 0
|
||||
|
||||
@@ -18,12 +17,4 @@ class Command(BaseCommand):
|
||||
raise CommandError("import requires 3 arguments: <org> <course> <data directory>")
|
||||
|
||||
org, course, data_dir = args
|
||||
|
||||
module_store = XMLModuleStore(org, course, data_dir, 'xmodule.raw_module.RawDescriptor', eager=True)
|
||||
for module in module_store.modules.itervalues():
|
||||
modulestore().create_item(module.location)
|
||||
if 'data' in module.definition:
|
||||
modulestore().update_item(module.location, module.definition['data'])
|
||||
if 'children' in module.definition:
|
||||
modulestore().update_children(module.location, module.definition['children'])
|
||||
modulestore().update_metadata(module.location, dict(module.metadata))
|
||||
import_from_xml(org, course, data_dir)
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse
|
||||
from util.json_request import expect_json
|
||||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from fs.osfs import OSFS
|
||||
from django.core.urlresolvers import reverse
|
||||
from xmodule.modulestore import Location
|
||||
from github_sync import repo_path_from_location, export_to_github
|
||||
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def index(request):
|
||||
courses = modulestore().get_items(['i4x', None, None, 'course', None])
|
||||
return render_to_response('index.html', {
|
||||
'courses': [(course.metadata['display_name'],
|
||||
reverse('course_index', args=[
|
||||
course.location.org,
|
||||
course.location.course,
|
||||
course.location.name]))
|
||||
for course in courses]
|
||||
})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def course_index(request, org, course, name):
|
||||
# TODO (cpennington): These need to be read in from the active user
|
||||
org = 'mit.edu'
|
||||
course = '6002xs12'
|
||||
name = '6.002_Spring_2012'
|
||||
course = modulestore().get_item(['i4x', org, course, 'course', name])
|
||||
weeks = course.get_children()
|
||||
return render_to_response('index.html', {'weeks': weeks})
|
||||
return render_to_response('course_index.html', {'weeks': weeks})
|
||||
|
||||
|
||||
def edit_item(request):
|
||||
@@ -28,10 +44,19 @@ def edit_item(request):
|
||||
})
|
||||
|
||||
|
||||
@expect_json
|
||||
def save_item(request):
|
||||
item_id = request.POST['id']
|
||||
data = json.loads(request.POST['data'])
|
||||
modulestore().update_item(item_id, data)
|
||||
|
||||
# Export the course back to github
|
||||
course_location = Location(item_id)._replace(category='course', name=None)
|
||||
courses = modulestore().get_items(course_location)
|
||||
for course in courses:
|
||||
repo_path = repo_path_from_location(course.location)
|
||||
export_to_github(course, repo_path, "CMS Edit")
|
||||
|
||||
return HttpResponse(json.dumps({}))
|
||||
|
||||
|
||||
|
||||
75
cms/djangoapps/github_sync/__init__.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from fs.osfs import OSFS
|
||||
from git import Repo, PushInfo
|
||||
|
||||
from contentstore import import_from_xml
|
||||
from xmodule.modulestore import Location
|
||||
|
||||
from .exceptions import GithubSyncError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def import_from_github(repo_settings):
|
||||
"""
|
||||
Imports data into the modulestore based on the XML stored on github
|
||||
|
||||
repo_settings is a dictionary with the following keys:
|
||||
path: file system path to the local git repo
|
||||
branch: name of the branch to track on github
|
||||
org: name of the organization to use in the imported course
|
||||
course: name of the coures to use in the imported course
|
||||
"""
|
||||
repo_path = repo_settings['path']
|
||||
|
||||
if not os.path.isdir(repo_path):
|
||||
Repo.clone_from(repo_settings['origin'], repo_path)
|
||||
|
||||
git_repo = Repo(repo_path)
|
||||
origin = git_repo.remotes.origin
|
||||
origin.fetch()
|
||||
|
||||
# Do a hard reset to the remote branch so that we have a clean import
|
||||
git_repo.git.checkout(repo_settings['branch'])
|
||||
git_repo.head.reset('origin/%s' % repo_settings['branch'], index=True, working_tree=True)
|
||||
|
||||
return git_repo.head.commit.hexsha, import_from_xml(repo_settings['org'], repo_settings['course'], repo_path)
|
||||
|
||||
|
||||
def repo_path_from_location(location):
|
||||
location = Location(location)
|
||||
for name, repo in settings.REPOS.items():
|
||||
if repo['org'] == location.org and repo['course'] == location.course:
|
||||
return repo['path']
|
||||
|
||||
|
||||
def export_to_github(course, repo_path, commit_message):
|
||||
fs = OSFS(repo_path)
|
||||
xml = course.export_to_xml(fs)
|
||||
|
||||
with fs.open('course.xml', 'w') as course_xml:
|
||||
course_xml.write(xml)
|
||||
|
||||
git_repo = Repo(repo_path)
|
||||
if git_repo.is_dirty():
|
||||
git_repo.git.add(A=True)
|
||||
git_repo.git.commit(m=commit_message)
|
||||
|
||||
origin = git_repo.remotes.origin
|
||||
if settings.MITX_FEATURES['GITHUB_PUSH']:
|
||||
push_infos = origin.push()
|
||||
if len(push_infos) > 1:
|
||||
log.error('Unexpectedly pushed multiple heads: {infos}'.format(
|
||||
infos="\n".join(str(info.summary) for info in push_infos)
|
||||
))
|
||||
|
||||
if push_infos[0].flags & PushInfo.ERROR:
|
||||
log.error('Failed push: flags={p.flags}, local_ref={p.local_ref}, '
|
||||
'remote_ref_string={p.remote_ref_string}, '
|
||||
'remote_ref={p.remote_ref}, old_commit={p.old_commit}, '
|
||||
'summary={p.summary})'.format(p=push_infos[0]))
|
||||
raise GithubSyncError('Failed to push: {info}'.format(
|
||||
info=str(push_infos[0].summary)
|
||||
))
|
||||
2
cms/djangoapps/github_sync/exceptions.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class GithubSyncError(Exception):
|
||||
pass
|
||||
96
cms/djangoapps/github_sync/tests/__init__.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from django.test import TestCase
|
||||
from path import path
|
||||
import shutil
|
||||
from github_sync import import_from_github, export_to_github, repo_path_from_location
|
||||
from git import Repo
|
||||
from django.conf import settings
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import Location
|
||||
from override_settings import override_settings
|
||||
from github_sync.exceptions import GithubSyncError
|
||||
|
||||
|
||||
class GithubSyncTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.working_dir = path(settings.TEST_ROOT)
|
||||
self.repo_dir = self.working_dir / 'local_repo'
|
||||
self.remote_dir = self.working_dir / 'remote_repo'
|
||||
shutil.copytree('common/test/data/toy', self.remote_dir)
|
||||
|
||||
remote = Repo.init(self.remote_dir)
|
||||
remote.git.add(A=True)
|
||||
remote.git.commit(m='Initial commit')
|
||||
remote.git.config("receive.denyCurrentBranch", "ignore")
|
||||
|
||||
modulestore().collection.drop()
|
||||
|
||||
self.import_revision, self.import_course = import_from_github({
|
||||
'path': self.repo_dir,
|
||||
'origin': self.remote_dir,
|
||||
'branch': 'master',
|
||||
'org': 'org',
|
||||
'course': 'course'
|
||||
})
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.repo_dir)
|
||||
shutil.rmtree(self.remote_dir)
|
||||
|
||||
def test_initialize_repo(self):
|
||||
"""
|
||||
Test that importing from github will create a repo if the repo doesn't already exist
|
||||
"""
|
||||
self.assertEquals(1, len(Repo(self.repo_dir).head.reference.log()))
|
||||
|
||||
def test_import_contents(self):
|
||||
"""
|
||||
Test that the import loads the correct course into the modulestore
|
||||
"""
|
||||
self.assertEquals('Toy Course', self.import_course.metadata['display_name'])
|
||||
self.assertIn(
|
||||
Location('i4x://org/course/chapter/Overview'),
|
||||
[child.location for child in self.import_course.get_children()])
|
||||
self.assertEquals(1, len(self.import_course.get_children()))
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': False})
|
||||
def test_export_no_pash(self):
|
||||
"""
|
||||
Test that with the GITHUB_PUSH feature disabled, no content is pushed to the remote
|
||||
"""
|
||||
export_to_github(self.import_course, self.repo_dir, 'Test no-push')
|
||||
self.assertEquals(1, Repo(self.remote_dir).head.commit.count())
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': True})
|
||||
def test_export_push(self):
|
||||
"""
|
||||
Test that with GITHUB_PUSH enabled, content is pushed to the remote
|
||||
"""
|
||||
self.import_course.metadata['display_name'] = 'Changed display name'
|
||||
export_to_github(self.import_course, self.repo_dir, 'Test push')
|
||||
self.assertEquals(2, Repo(self.remote_dir).head.commit.count())
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': True})
|
||||
def test_export_conflict(self):
|
||||
"""
|
||||
Test that if there is a conflict when pushing to the remote repo, nothing is pushed and an exception is raised
|
||||
"""
|
||||
self.import_course.metadata['display_name'] = 'Changed display name'
|
||||
|
||||
remote = Repo(self.remote_dir)
|
||||
remote.git.commit(allow_empty=True, m="Testing conflict commit")
|
||||
|
||||
self.assertRaises(GithubSyncError, export_to_github, self.import_course, self.repo_dir, 'Test push')
|
||||
self.assertEquals(2, remote.head.reference.commit.count())
|
||||
self.assertEquals("Testing conflict commit\n", remote.head.reference.commit.message)
|
||||
|
||||
|
||||
@override_settings(REPOS={'namea': {'path': 'patha', 'org': 'orga', 'course': 'coursea'},
|
||||
'nameb': {'path': 'pathb', 'org': 'orgb', 'course': 'courseb'}})
|
||||
class RepoPathLookupTestCase(TestCase):
|
||||
def test_successful_lookup(self):
|
||||
self.assertEquals('patha', repo_path_from_location('i4x://orga/coursea/course/foo'))
|
||||
self.assertEquals('pathb', repo_path_from_location('i4x://orgb/courseb/course/foo'))
|
||||
|
||||
def test_failed_lookup(self):
|
||||
self.assertEquals(None, repo_path_from_location('i4x://c/c/course/foo'))
|
||||
53
cms/djangoapps/github_sync/tests/test_views.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
from django.test.client import Client
|
||||
from django.test import TestCase
|
||||
from mock import patch, Mock
|
||||
from override_settings import override_settings
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@override_settings(REPOS={'repo': {'path': 'path', 'branch': 'branch'}})
|
||||
class PostReceiveTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github')
|
||||
def test_non_branch(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/tags/foo'})
|
||||
})
|
||||
self.assertFalse(import_from_github.called)
|
||||
self.assertFalse(export_to_github.called)
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github')
|
||||
def test_non_watched_repo(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/heads/branch',
|
||||
'repository': {'name': 'bad_repo'}})
|
||||
})
|
||||
self.assertFalse(import_from_github.called)
|
||||
self.assertFalse(export_to_github.called)
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github')
|
||||
def test_non_tracked_branch(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/heads/non_branch',
|
||||
'repository': {'name': 'repo'}})
|
||||
})
|
||||
self.assertFalse(import_from_github.called)
|
||||
self.assertFalse(export_to_github.called)
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github', return_value=(Mock(), Mock()))
|
||||
def test_tracked_branch(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/heads/branch',
|
||||
'repository': {'name': 'repo'}})
|
||||
})
|
||||
import_from_github.assert_called_with(settings.REPOS['repo'])
|
||||
mock_revision, mock_course = import_from_github.return_value
|
||||
export_to_github.assert_called_with(mock_course, 'path', "Changes from cms import of revision %s" % mock_revision)
|
||||
|
||||
52
cms/djangoapps/github_sync/views.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import logging
|
||||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django_future.csrf import csrf_exempt
|
||||
|
||||
from . import import_from_github, export_to_github
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def github_post_receive(request):
|
||||
"""
|
||||
This view recieves post-receive requests from github whenever one of
|
||||
the watched repositiories changes.
|
||||
|
||||
It is responsible for updating the relevant local git repo,
|
||||
importing the new version of the course (if anything changed),
|
||||
and then pushing back to github any changes that happened as part of the
|
||||
import.
|
||||
|
||||
The github request format is described here: https://help.github.com/articles/post-receive-hooks
|
||||
"""
|
||||
|
||||
payload = json.loads(request.POST['payload'])
|
||||
|
||||
ref = payload['ref']
|
||||
|
||||
if not ref.startswith('refs/heads/'):
|
||||
log.info('Ignore changes to non-branch ref %s' % ref)
|
||||
return HttpResponse('Ignoring non-branch')
|
||||
|
||||
branch_name = ref.replace('refs/heads/', '', 1)
|
||||
|
||||
repo_name = payload['repository']['name']
|
||||
|
||||
if repo_name not in settings.REPOS:
|
||||
log.info('No repository matching %s found' % repo_name)
|
||||
return HttpResponse('No Repo Found')
|
||||
|
||||
repo = settings.REPOS[repo_name]
|
||||
|
||||
if repo['branch'] != branch_name:
|
||||
log.info('Ignoring changes to non-tracked branch %s in repo %s' % (branch_name, repo_name))
|
||||
return HttpResponse('Ignoring non-tracked branch')
|
||||
|
||||
revision, course = import_from_github(repo)
|
||||
export_to_github(course, repo['path'], "Changes from cms import of revision %s" % revision)
|
||||
|
||||
return HttpResponse('Push recieved')
|
||||
@@ -24,12 +24,14 @@ import tempfile
|
||||
import os.path
|
||||
import os
|
||||
import errno
|
||||
import glob2
|
||||
from path import path
|
||||
|
||||
############################ FEATURE CONFIGURATION #############################
|
||||
|
||||
MITX_FEATURES = {
|
||||
'USE_DJANGO_PIPELINE': True,
|
||||
'GITHUB_PUSH': False,
|
||||
}
|
||||
|
||||
############################# SET PATH INFORMATION #############################
|
||||
@@ -57,6 +59,10 @@ MAKO_TEMPLATES['main'] = [
|
||||
COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates'
|
||||
]
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
PROJECT_ROOT / "templates",
|
||||
)
|
||||
|
||||
MITX_ROOT_URL = ''
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
@@ -67,6 +73,9 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.core.context_processors.csrf', # necessary for csrf protection
|
||||
)
|
||||
|
||||
################################# Jasmine ###################################
|
||||
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
|
||||
|
||||
################################# Middleware ###################################
|
||||
# List of finder classes that know how to find static files in
|
||||
# various locations.
|
||||
@@ -183,12 +192,16 @@ for xmodule in XModuleDescriptor.load_classes() + [RawDescriptor]:
|
||||
|
||||
PIPELINE_JS = {
|
||||
'main': {
|
||||
'source_filenames': ['coffee/main.coffee', 'coffee/unit.coffee'],
|
||||
'output_filename': 'js/main.js',
|
||||
'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')],
|
||||
'output_filename': 'js/application.js',
|
||||
},
|
||||
'module-js': {
|
||||
'source_filenames': module_js_sources,
|
||||
'output_filename': 'js/modules.js',
|
||||
},
|
||||
'spec': {
|
||||
'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/spec/**/*.coffee')],
|
||||
'output_filename': 'js/spec.js'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,4 +245,7 @@ INSTALLED_APPS = (
|
||||
# For asset pipelining
|
||||
'pipeline',
|
||||
'staticfiles',
|
||||
|
||||
# For testing
|
||||
'django_jasmine',
|
||||
)
|
||||
|
||||
@@ -29,8 +29,48 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
REPO_ROOT = ENV_ROOT / "content"
|
||||
|
||||
REPOS = {
|
||||
'edx4edx': {
|
||||
'path': REPO_ROOT / "edx4edx",
|
||||
'org': 'edx',
|
||||
'course': 'edx4edx',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/edx4edx.git',
|
||||
},
|
||||
'6002x-fall-2012': {
|
||||
'path': REPO_ROOT / '6002x-fall-2012',
|
||||
'org': 'mit.edu',
|
||||
'course': '6.002x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/6002x-fall-2012.git',
|
||||
},
|
||||
'6.00x': {
|
||||
'path': REPO_ROOT / '6.00x',
|
||||
'org': 'mit.edu',
|
||||
'course': '6.00x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/6.00x.git',
|
||||
},
|
||||
'7.00x': {
|
||||
'path': REPO_ROOT / '7.00x',
|
||||
'org': 'mit.edu',
|
||||
'course': '7.00x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/7.00x.git',
|
||||
},
|
||||
'3.091x': {
|
||||
'path': REPO_ROOT / '3.091x',
|
||||
'org': 'mit.edu',
|
||||
'course': '3.091x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/3.091x.git',
|
||||
},
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
# functioning cache -- it relies on caching to load its settings in places.
|
||||
# In staging/prod envs, the sessions also live here.
|
||||
'default': {
|
||||
|
||||
@@ -17,10 +17,18 @@ for app in os.listdir(PROJECT_ROOT / 'djangoapps'):
|
||||
NOSE_ARGS += ['--cover-package', app]
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
||||
TEST_ROOT = 'test_root'
|
||||
|
||||
MODULESTORE = {
|
||||
'host': 'localhost',
|
||||
'db': 'mongo_base',
|
||||
'collection': 'key_store',
|
||||
'default': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
|
||||
'OPTIONS': {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'modulestore',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DATABASES = {
|
||||
|
||||
8
cms/static/coffee/files.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"js_files": [
|
||||
"/static/js/vendor/jquery.min.js",
|
||||
"/static/js/vendor/json2.js",
|
||||
"/static/js/vendor/underscore-min.js",
|
||||
"/static/js/vendor/backbone-min.js"
|
||||
]
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
class @CMS
|
||||
@setHeight = =>
|
||||
windowHeight = $(this).height()
|
||||
@contentHeight = windowHeight - 29
|
||||
|
||||
@bind = =>
|
||||
$('a.module-edit').click ->
|
||||
CMS.edit_item($(this).attr('id'))
|
||||
return false
|
||||
$(window).bind('resize', CMS.setHeight)
|
||||
|
||||
@edit_item = (id) =>
|
||||
$.get('/edit_item', {id: id}, (data) =>
|
||||
$('#module-html').empty().append(data)
|
||||
CMS.bind()
|
||||
$('body.content .cal').css('height', @contentHeight)
|
||||
$('body').addClass('content')
|
||||
$('section.edit-pane').show()
|
||||
new Unit('unit-wrapper', id)
|
||||
)
|
||||
|
||||
$ ->
|
||||
$.ajaxSetup
|
||||
headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
|
||||
$('section.main-content').children().hide()
|
||||
$('.editable').inlineEdit()
|
||||
$('.editable-textarea').inlineEdit({control: 'textarea'})
|
||||
|
||||
heighest = 0
|
||||
$('.cal ol > li').each ->
|
||||
heighest = if $(this).height() > heighest then $(this).height() else heighest
|
||||
|
||||
$('.cal ol > li').css('height',heighest + 'px')
|
||||
|
||||
$('.add-new-section').click -> return false
|
||||
|
||||
$('.new-week .close').click ->
|
||||
$(this).parents('.new-week').hide()
|
||||
$('p.add-new-week').show()
|
||||
return false
|
||||
|
||||
$('.save-update').click ->
|
||||
$(this).parent().parent().hide()
|
||||
return false
|
||||
|
||||
# $('html').keypress ->
|
||||
# $('.wip').css('visibility', 'visible')
|
||||
|
||||
setHeight = ->
|
||||
windowHeight = $(this).height()
|
||||
contentHeight = windowHeight - 29
|
||||
|
||||
$('section.main-content > section').css('min-height', contentHeight)
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
|
||||
$('.edit-week').click ->
|
||||
$('body').addClass('content')
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('a.week-edit').click ->
|
||||
$('body').addClass('content')
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('a.sequence-edit').click ->
|
||||
$('body').addClass('content')
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('a.module-edit').click ->
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
|
||||
$(document).ready(setHeight)
|
||||
$(window).bind('resize', setHeight)
|
||||
|
||||
$('.video-new a').click ->
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('.problem-new a').click ->
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
CMS.setHeight()
|
||||
CMS.bind()
|
||||
|
||||
18
cms/static/coffee/spec/helpers.coffee
Normal file
@@ -0,0 +1,18 @@
|
||||
# Stub jQuery.cookie
|
||||
@stubCookies =
|
||||
csrftoken: "stubCSRFToken"
|
||||
|
||||
jQuery.cookie = (key, value) =>
|
||||
if value?
|
||||
@stubCookies[key] = value
|
||||
else
|
||||
@stubCookies[key]
|
||||
|
||||
# Path Jasmine's `it` method to raise an error when the test is not defined.
|
||||
# This is helpful when writing the specs first before writing the test.
|
||||
@it = (desc, func) ->
|
||||
if func?
|
||||
jasmine.getEnv().it(desc, func)
|
||||
else
|
||||
jasmine.getEnv().it desc, ->
|
||||
throw "test is undefined"
|
||||
90
cms/static/coffee/spec/main_spec.coffee
Normal file
@@ -0,0 +1,90 @@
|
||||
describe "CMS", ->
|
||||
beforeEach ->
|
||||
CMS.unbind()
|
||||
|
||||
it "should iniitalize Models", ->
|
||||
expect(CMS.Models).toBeDefined()
|
||||
|
||||
it "should initialize Views", ->
|
||||
expect(CMS.Views).toBeDefined()
|
||||
|
||||
describe "start", ->
|
||||
beforeEach ->
|
||||
@element = $("<div>")
|
||||
spyOn(CMS.Views, "Course").andReturn(jasmine.createSpyObj("Course", ["render"]))
|
||||
CMS.start(@element)
|
||||
|
||||
it "create the Course", ->
|
||||
expect(CMS.Views.Course).toHaveBeenCalledWith(el: @element)
|
||||
expect(CMS.Views.Course().render).toHaveBeenCalled()
|
||||
|
||||
describe "view stack", ->
|
||||
beforeEach ->
|
||||
@currentView = jasmine.createSpy("currentView")
|
||||
CMS.viewStack = [@currentView]
|
||||
|
||||
describe "replaceView", ->
|
||||
beforeEach ->
|
||||
@newView = jasmine.createSpy("newView")
|
||||
CMS.on("content.show", (@expectedView) =>)
|
||||
CMS.replaceView(@newView)
|
||||
|
||||
it "replace the views on the viewStack", ->
|
||||
expect(CMS.viewStack).toEqual([@newView])
|
||||
|
||||
it "trigger content.show on CMS", ->
|
||||
expect(@expectedView).toEqual(@newView)
|
||||
|
||||
describe "pushView", ->
|
||||
beforeEach ->
|
||||
@newView = jasmine.createSpy("newView")
|
||||
CMS.on("content.show", (@expectedView) =>)
|
||||
CMS.pushView(@newView)
|
||||
|
||||
it "push new view onto viewStack", ->
|
||||
expect(CMS.viewStack).toEqual([@currentView, @newView])
|
||||
|
||||
it "trigger content.show on CMS", ->
|
||||
expect(@expectedView).toEqual(@newView)
|
||||
|
||||
describe "popView", ->
|
||||
it "remove the current view from the viewStack", ->
|
||||
CMS.popView()
|
||||
expect(CMS.viewStack).toEqual([])
|
||||
|
||||
describe "when there's no view on the viewStack", ->
|
||||
beforeEach ->
|
||||
CMS.viewStack = [@currentView]
|
||||
CMS.on("content.hide", => @eventTriggered = true)
|
||||
CMS.popView()
|
||||
|
||||
it "trigger content.hide on CMS", ->
|
||||
expect(@eventTriggered).toBeTruthy
|
||||
|
||||
describe "when there's previous view on the viewStack", ->
|
||||
beforeEach ->
|
||||
@parentView = jasmine.createSpyObj("parentView", ["delegateEvents"])
|
||||
CMS.viewStack = [@parentView, @currentView]
|
||||
CMS.on("content.show", (@expectedView) =>)
|
||||
CMS.popView()
|
||||
|
||||
it "trigger content.show with the previous view on CMS", ->
|
||||
expect(@expectedView).toEqual @parentView
|
||||
|
||||
it "re-bind events on the view", ->
|
||||
expect(@parentView.delegateEvents).toHaveBeenCalled()
|
||||
|
||||
describe "main helper", ->
|
||||
beforeEach ->
|
||||
@previousAjaxSettings = $.extend(true, {}, $.ajaxSettings)
|
||||
window.stubCookies["csrftoken"] = "stubCSRFToken"
|
||||
$(document).ready()
|
||||
|
||||
afterEach ->
|
||||
$.ajaxSettings = @previousAjaxSettings
|
||||
|
||||
it "turn on Backbone emulateHTTP", ->
|
||||
expect(Backbone.emulateHTTP).toBeTruthy()
|
||||
|
||||
it "setup AJAX CSRF token", ->
|
||||
expect($.ajaxSettings.headers["X-CSRFToken"]).toEqual("stubCSRFToken")
|
||||
65
cms/static/coffee/spec/models/module_spec.coffee
Normal file
@@ -0,0 +1,65 @@
|
||||
describe "CMS.Models.Module", ->
|
||||
it "set the correct URL", ->
|
||||
expect(new CMS.Models.Module().url).toEqual("/save_item")
|
||||
|
||||
it "set the correct default", ->
|
||||
expect(new CMS.Models.Module().defaults).toEqual({data: ""})
|
||||
|
||||
describe "loadModule", ->
|
||||
describe "when the module exists", ->
|
||||
beforeEach ->
|
||||
@fakeModule = jasmine.createSpy("fakeModuleObject")
|
||||
window.FakeModule = jasmine.createSpy("FakeModule").andReturn(@fakeModule)
|
||||
@module = new CMS.Models.Module(type: "FakeModule")
|
||||
@stubElement = $("<div>")
|
||||
@module.loadModule(@stubElement)
|
||||
|
||||
afterEach ->
|
||||
window.FakeModule = undefined
|
||||
|
||||
it "initialize the module", ->
|
||||
expect(window.FakeModule).toHaveBeenCalledWith(@stubElement)
|
||||
expect(@module.module).toEqual(@fakeModule)
|
||||
|
||||
describe "when the module does not exists", ->
|
||||
beforeEach ->
|
||||
@previousConsole = window.console
|
||||
window.console = jasmine.createSpyObj("fakeConsole", ["error"])
|
||||
@module = new CMS.Models.Module(type: "HTML")
|
||||
@module.loadModule($("<div>"))
|
||||
|
||||
afterEach ->
|
||||
window.console = @previousConsole
|
||||
|
||||
it "print out error to log", ->
|
||||
expect(window.console.error).toHaveBeenCalledWith("Unable to load HTML.")
|
||||
|
||||
|
||||
describe "editUrl", ->
|
||||
it "construct the correct URL based on id", ->
|
||||
expect(new CMS.Models.Module(id: "i4x://mit.edu/module/html_123").editUrl())
|
||||
.toEqual("/edit_item?id=i4x%3A%2F%2Fmit.edu%2Fmodule%2Fhtml_123")
|
||||
|
||||
describe "save", ->
|
||||
beforeEach ->
|
||||
spyOn(Backbone.Model.prototype, "save")
|
||||
@module = new CMS.Models.Module()
|
||||
|
||||
describe "when the module exists", ->
|
||||
beforeEach ->
|
||||
@module.module = jasmine.createSpyObj("FakeModule", ["save"])
|
||||
@module.module.save.andReturn("module data")
|
||||
@module.save()
|
||||
|
||||
it "set the data and call save on the module", ->
|
||||
expect(@module.get("data")).toEqual("\"module data\"")
|
||||
|
||||
it "call save on the backbone model", ->
|
||||
expect(Backbone.Model.prototype.save).toHaveBeenCalled()
|
||||
|
||||
describe "when the module does not exists", ->
|
||||
beforeEach ->
|
||||
@module.save()
|
||||
|
||||
it "call save on the backbone model", ->
|
||||
expect(Backbone.Model.prototype.save).toHaveBeenCalled()
|
||||
85
cms/static/coffee/spec/views/course_spec.coffee
Normal file
@@ -0,0 +1,85 @@
|
||||
describe "CMS.Views.Course", ->
|
||||
beforeEach ->
|
||||
setFixtures """
|
||||
<section id="main-section">
|
||||
<section class="main-content"></section>
|
||||
<ol id="weeks">
|
||||
<li class="cal week-one" style="height: 50px"></li>
|
||||
<li class="cal week-two" style="height: 100px"></li>
|
||||
</ol>
|
||||
</section>
|
||||
"""
|
||||
CMS.unbind()
|
||||
|
||||
describe "render", ->
|
||||
beforeEach ->
|
||||
spyOn(CMS.Views, "Week").andReturn(jasmine.createSpyObj("Week", ["render"]))
|
||||
new CMS.Views.Course(el: $("#main-section")).render()
|
||||
|
||||
it "create week view for each week",->
|
||||
expect(CMS.Views.Week.calls[0].args[0])
|
||||
.toEqual({ el: $(".week-one").get(0), height: 101 })
|
||||
expect(CMS.Views.Week.calls[1].args[0])
|
||||
.toEqual({ el: $(".week-two").get(0), height: 101 })
|
||||
|
||||
describe "on content.show", ->
|
||||
beforeEach ->
|
||||
@view = new CMS.Views.Course(el: $("#main-section"))
|
||||
@subView = jasmine.createSpyObj("subView", ["render"])
|
||||
@subView.render.andReturn(el: "Subview Content")
|
||||
spyOn(@view, "contentHeight").andReturn(100)
|
||||
CMS.trigger("content.show", @subView)
|
||||
|
||||
afterEach ->
|
||||
$("body").removeClass("content")
|
||||
|
||||
it "add content class to body", ->
|
||||
expect($("body").attr("class")).toEqual("content")
|
||||
|
||||
it "replace content in .main-content", ->
|
||||
expect($(".main-content")).toHaveHtml("Subview Content")
|
||||
|
||||
it "set height on calendar", ->
|
||||
expect($(".cal")).toHaveCss(height: "100px")
|
||||
|
||||
it "set minimum height on all sections", ->
|
||||
expect($("#main-section>section")).toHaveCss(minHeight: "100px")
|
||||
|
||||
describe "on content.hide", ->
|
||||
beforeEach ->
|
||||
$("body").addClass("content")
|
||||
@view = new CMS.Views.Course(el: $("#main-section"))
|
||||
$(".cal").css(height: 100)
|
||||
$("#main-section>section").css(minHeight: 100)
|
||||
CMS.trigger("content.hide")
|
||||
|
||||
afterEach ->
|
||||
$("body").removeClass("content")
|
||||
|
||||
it "remove content class from body", ->
|
||||
expect($("body").attr("class")).toEqual("")
|
||||
|
||||
it "remove content from .main-content", ->
|
||||
expect($(".main-content")).toHaveHtml("")
|
||||
|
||||
it "reset height on calendar", ->
|
||||
expect($(".cal")).not.toHaveCss(height: "100px")
|
||||
|
||||
it "reset minimum height on all sections", ->
|
||||
expect($("#main-section>section")).not.toHaveCss(minHeight: "100px")
|
||||
|
||||
describe "maxWeekHeight", ->
|
||||
it "return maximum height of the week element", ->
|
||||
@view = new CMS.Views.Course(el: $("#main-section"))
|
||||
expect(@view.maxWeekHeight()).toEqual(101)
|
||||
|
||||
describe "contentHeight", ->
|
||||
beforeEach ->
|
||||
$("body").append($('<header id="test">').height(100).hide())
|
||||
|
||||
afterEach ->
|
||||
$("body>header#test").remove()
|
||||
|
||||
it "return the window height minus the header bar", ->
|
||||
@view = new CMS.Views.Course(el: $("#main-section"))
|
||||
expect(@view.contentHeight()).toEqual($(window).height() - 100)
|
||||
81
cms/static/coffee/spec/views/module_edit_spec.coffee
Normal file
@@ -0,0 +1,81 @@
|
||||
describe "CMS.Views.ModuleEdit", ->
|
||||
beforeEach ->
|
||||
@stubModule = jasmine.createSpyObj("Module", ["editUrl", "loadModule"])
|
||||
spyOn($.fn, "load")
|
||||
setFixtures """
|
||||
<div id="module-edit">
|
||||
<a href="#" class="save-update">save</a>
|
||||
<a href="#" class="cancel">cancel</a>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#" class="module-edit" data-id="i4x://mitx.edu/course/module" data-type="html">submodule</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
"""
|
||||
CMS.unbind()
|
||||
|
||||
describe "defaults", ->
|
||||
it "set the correct tagName", ->
|
||||
expect(new CMS.Views.ModuleEdit(model: @stubModule).tagName).toEqual("section")
|
||||
|
||||
it "set the correct className", ->
|
||||
expect(new CMS.Views.ModuleEdit(model: @stubModule).className).toEqual("edit-pane")
|
||||
|
||||
describe "view creation", ->
|
||||
beforeEach ->
|
||||
@stubModule.editUrl.andReturn("/edit_item?id=stub_module")
|
||||
new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
|
||||
|
||||
it "load the edit from via ajax and pass to the model", ->
|
||||
expect($.fn.load).toHaveBeenCalledWith("/edit_item?id=stub_module", jasmine.any(Function))
|
||||
if $.fn.load.mostRecentCall
|
||||
$.fn.load.mostRecentCall.args[1]()
|
||||
expect(@stubModule.loadModule).toHaveBeenCalledWith($("#module-edit").get(0))
|
||||
|
||||
describe "save", ->
|
||||
beforeEach ->
|
||||
@stubJqXHR = jasmine.createSpy("stubJqXHR")
|
||||
@stubJqXHR.success = jasmine.createSpy("stubJqXHR.success").andReturn(@stubJqXHR)
|
||||
@stubJqXHR.error= jasmine.createSpy("stubJqXHR.success").andReturn(@stubJqXHR)
|
||||
@stubModule.save = jasmine.createSpy("stubModule.save").andReturn(@stubJqXHR)
|
||||
new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
|
||||
spyOn(window, "alert")
|
||||
$(".save-update").click()
|
||||
|
||||
it "call save on the model", ->
|
||||
expect(@stubModule.save).toHaveBeenCalled()
|
||||
|
||||
it "alert user on success", ->
|
||||
@stubJqXHR.success.mostRecentCall.args[0]()
|
||||
expect(window.alert).toHaveBeenCalledWith("Your changes have been saved.")
|
||||
|
||||
it "alert user on error", ->
|
||||
@stubJqXHR.error.mostRecentCall.args[0]()
|
||||
expect(window.alert).toHaveBeenCalledWith("There was an error saving your changes. Please try again.")
|
||||
|
||||
describe "cancel", ->
|
||||
beforeEach ->
|
||||
spyOn(CMS, "popView")
|
||||
@view = new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
|
||||
$(".cancel").click()
|
||||
|
||||
it "pop current view from viewStack", ->
|
||||
expect(CMS.popView).toHaveBeenCalled()
|
||||
|
||||
describe "editSubmodule", ->
|
||||
beforeEach ->
|
||||
@view = new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
|
||||
spyOn(CMS, "pushView")
|
||||
spyOn(CMS.Views, "ModuleEdit")
|
||||
.andReturn(@view = jasmine.createSpy("Views.ModuleEdit"))
|
||||
spyOn(CMS.Models, "Module")
|
||||
.andReturn(@model = jasmine.createSpy("Models.Module"))
|
||||
$(".module-edit").click()
|
||||
|
||||
it "push another module editing view into viewStack", ->
|
||||
expect(CMS.pushView).toHaveBeenCalledWith @view
|
||||
expect(CMS.Views.ModuleEdit).toHaveBeenCalledWith model: @model
|
||||
expect(CMS.Models.Module).toHaveBeenCalledWith
|
||||
id: "i4x://mitx.edu/course/module"
|
||||
type: "html"
|
||||
24
cms/static/coffee/spec/views/module_spec.coffee
Normal file
@@ -0,0 +1,24 @@
|
||||
describe "CMS.Views.Module", ->
|
||||
beforeEach ->
|
||||
setFixtures """
|
||||
<div id="module" data-id="i4x://mitx.edu/course/module" data-type="html">
|
||||
<a href="#" class="module-edit">edit</a>
|
||||
</div>
|
||||
"""
|
||||
|
||||
describe "edit", ->
|
||||
beforeEach ->
|
||||
@view = new CMS.Views.Module(el: $("#module"))
|
||||
spyOn(CMS, "replaceView")
|
||||
spyOn(CMS.Views, "ModuleEdit")
|
||||
.andReturn(@view = jasmine.createSpy("Views.ModuleEdit"))
|
||||
spyOn(CMS.Models, "Module")
|
||||
.andReturn(@model = jasmine.createSpy("Models.Module"))
|
||||
$(".module-edit").click()
|
||||
|
||||
it "replace the main view with ModuleEdit view", ->
|
||||
expect(CMS.replaceView).toHaveBeenCalledWith @view
|
||||
expect(CMS.Views.ModuleEdit).toHaveBeenCalledWith model: @model
|
||||
expect(CMS.Models.Module).toHaveBeenCalledWith
|
||||
id: "i4x://mitx.edu/course/module"
|
||||
type: "html"
|
||||
7
cms/static/coffee/spec/views/week_edit_spec.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
describe "CMS.Views.WeekEdit", ->
|
||||
describe "defaults", ->
|
||||
it "set the correct tagName", ->
|
||||
expect(new CMS.Views.WeekEdit().tagName).toEqual("section")
|
||||
|
||||
it "set the correct className", ->
|
||||
expect(new CMS.Views.WeekEdit().className).toEqual("edit-pane")
|
||||
67
cms/static/coffee/spec/views/week_spec.coffee
Normal file
@@ -0,0 +1,67 @@
|
||||
describe "CMS.Views.Week", ->
|
||||
beforeEach ->
|
||||
setFixtures """
|
||||
<div id="week" data-id="i4x://mitx.edu/course/week">
|
||||
<div class="editable"></div>
|
||||
<textarea class="editable-textarea"></textarea>
|
||||
<a href="#" class="week-edit" >edit</a>
|
||||
<ul class="modules">
|
||||
<li id="module-one" class="module"></li>
|
||||
<li id="module-two" class="module"></li>
|
||||
</ul>
|
||||
</div>
|
||||
"""
|
||||
CMS.unbind()
|
||||
|
||||
describe "render", ->
|
||||
beforeEach ->
|
||||
spyOn(CMS.Views, "Module").andReturn(jasmine.createSpyObj("Module", ["render"]))
|
||||
$.fn.inlineEdit = jasmine.createSpy("$.fn.inlineEdit")
|
||||
@view = new CMS.Views.Week(el: $("#week"), height: 100).render()
|
||||
|
||||
it "set the height of the element", ->
|
||||
expect(@view.el).toHaveCss(height: "100px")
|
||||
|
||||
it "make .editable as inline editor", ->
|
||||
expect($.fn.inlineEdit.calls[0].object.get(0))
|
||||
.toEqual($(".editable").get(0))
|
||||
|
||||
it "make .editable-test as inline editor", ->
|
||||
expect($.fn.inlineEdit.calls[1].object.get(0))
|
||||
.toEqual($(".editable-textarea").get(0))
|
||||
|
||||
it "create module subview for each module", ->
|
||||
expect(CMS.Views.Module.calls[0].args[0])
|
||||
.toEqual({ el: $("#module-one").get(0) })
|
||||
expect(CMS.Views.Module.calls[1].args[0])
|
||||
.toEqual({ el: $("#module-two").get(0) })
|
||||
|
||||
describe "edit", ->
|
||||
beforeEach ->
|
||||
new CMS.Views.Week(el: $("#week"), height: 100).render()
|
||||
spyOn(CMS, "replaceView")
|
||||
spyOn(CMS.Views, "WeekEdit")
|
||||
.andReturn(@view = jasmine.createSpy("Views.WeekEdit"))
|
||||
$(".week-edit").click()
|
||||
|
||||
it "replace the content with edit week view", ->
|
||||
expect(CMS.replaceView).toHaveBeenCalledWith @view
|
||||
expect(CMS.Views.WeekEdit).toHaveBeenCalled()
|
||||
|
||||
describe "on content.show", ->
|
||||
beforeEach ->
|
||||
@view = new CMS.Views.Week(el: $("#week"), height: 100).render()
|
||||
@view.$el.height("")
|
||||
@view.setHeight()
|
||||
|
||||
it "set the correct height", ->
|
||||
expect(@view.el).toHaveCss(height: "100px")
|
||||
|
||||
describe "on content.hide", ->
|
||||
beforeEach ->
|
||||
@view = new CMS.Views.Week(el: $("#week"), height: 100).render()
|
||||
@view.$el.height("100px")
|
||||
@view.resetHeight()
|
||||
|
||||
it "remove height from the element", ->
|
||||
expect(@view.el).not.toHaveCss(height: "100px")
|
||||
35
cms/static/coffee/src/main.coffee
Normal file
@@ -0,0 +1,35 @@
|
||||
@CMS =
|
||||
Models: {}
|
||||
Views: {}
|
||||
|
||||
viewStack: []
|
||||
|
||||
start: (el) ->
|
||||
new CMS.Views.Course(el: el).render()
|
||||
|
||||
replaceView: (view) ->
|
||||
@viewStack = [view]
|
||||
CMS.trigger('content.show', view)
|
||||
|
||||
pushView: (view) ->
|
||||
@viewStack.push(view)
|
||||
CMS.trigger('content.show', view)
|
||||
|
||||
popView: ->
|
||||
@viewStack.pop()
|
||||
if _.isEmpty(@viewStack)
|
||||
CMS.trigger('content.hide')
|
||||
else
|
||||
view = _.last(@viewStack)
|
||||
CMS.trigger('content.show', view)
|
||||
view.delegateEvents()
|
||||
|
||||
_.extend CMS, Backbone.Events
|
||||
|
||||
$ ->
|
||||
Backbone.emulateHTTP = true
|
||||
|
||||
$.ajaxSetup
|
||||
headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
|
||||
|
||||
CMS.start($('section.main-container'))
|
||||
17
cms/static/coffee/src/models/module.coffee
Normal file
@@ -0,0 +1,17 @@
|
||||
class CMS.Models.Module extends Backbone.Model
|
||||
url: '/save_item'
|
||||
defaults:
|
||||
data: ''
|
||||
|
||||
loadModule: (element) ->
|
||||
try
|
||||
@module = new window[@get('type')](element)
|
||||
catch TypeError
|
||||
console.error "Unable to load #{@get('type')}." if console
|
||||
|
||||
editUrl: ->
|
||||
"/edit_item?#{$.param(id: @get('id'))}"
|
||||
|
||||
save: (args...) ->
|
||||
@set(data: JSON.stringify(@module.save())) if @module
|
||||
super(args...)
|
||||
28
cms/static/coffee/src/views/course.coffee
Normal file
@@ -0,0 +1,28 @@
|
||||
class CMS.Views.Course extends Backbone.View
|
||||
initialize: ->
|
||||
CMS.on('content.show', @showContent)
|
||||
CMS.on('content.hide', @hideContent)
|
||||
|
||||
render: ->
|
||||
@$('#weeks > li').each (index, week) =>
|
||||
new CMS.Views.Week(el: week, height: @maxWeekHeight()).render()
|
||||
return @
|
||||
|
||||
showContent: (subview) =>
|
||||
$('body').addClass('content')
|
||||
@$('.main-content').html(subview.render().el)
|
||||
@$('.cal').css height: @contentHeight()
|
||||
@$('>section').css minHeight: @contentHeight()
|
||||
|
||||
hideContent: =>
|
||||
$('body').removeClass('content')
|
||||
@$('.main-content').empty()
|
||||
@$('.cal').css height: ''
|
||||
@$('>section').css minHeight: ''
|
||||
|
||||
maxWeekHeight: ->
|
||||
weekElementBorderSize = 1
|
||||
_.max($('#weeks > li').map -> $(this).height()) + weekElementBorderSize
|
||||
|
||||
contentHeight: ->
|
||||
$(window).height() - $('body>header').outerHeight()
|
||||
7
cms/static/coffee/src/views/module.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
class CMS.Views.Module extends Backbone.View
|
||||
events:
|
||||
"click .module-edit": "edit"
|
||||
|
||||
edit: (event) =>
|
||||
event.preventDefault()
|
||||
CMS.replaceView(new CMS.Views.ModuleEdit(model: new CMS.Models.Module(id: @$el.data('id'), type: @$el.data('type'))))
|
||||
28
cms/static/coffee/src/views/module_edit.coffee
Normal file
@@ -0,0 +1,28 @@
|
||||
class CMS.Views.ModuleEdit extends Backbone.View
|
||||
tagName: 'section'
|
||||
className: 'edit-pane'
|
||||
|
||||
events:
|
||||
'click .cancel': 'cancel'
|
||||
'click .module-edit': 'editSubmodule'
|
||||
'click .save-update': 'save'
|
||||
|
||||
initialize: ->
|
||||
@$el.load @model.editUrl(), =>
|
||||
@model.loadModule(@el)
|
||||
|
||||
save: (event) ->
|
||||
event.preventDefault()
|
||||
@model.save().success(->
|
||||
alert("Your changes have been saved.")
|
||||
).error(->
|
||||
alert("There was an error saving your changes. Please try again.")
|
||||
)
|
||||
|
||||
cancel: (event) ->
|
||||
event.preventDefault()
|
||||
CMS.popView()
|
||||
|
||||
editSubmodule: (event) ->
|
||||
event.preventDefault()
|
||||
CMS.pushView(new CMS.Views.ModuleEdit(model: new CMS.Models.Module(id: $(event.target).data('id'), type: $(event.target).data('type'))))
|
||||
25
cms/static/coffee/src/views/week.coffee
Normal file
@@ -0,0 +1,25 @@
|
||||
class CMS.Views.Week extends Backbone.View
|
||||
events:
|
||||
'click .week-edit': 'edit'
|
||||
|
||||
initialize: ->
|
||||
CMS.on('content.show', @resetHeight)
|
||||
CMS.on('content.hide', @setHeight)
|
||||
|
||||
render: ->
|
||||
@setHeight()
|
||||
@$('.editable').inlineEdit()
|
||||
@$('.editable-textarea').inlineEdit(control: 'textarea')
|
||||
@$('.modules .module').each ->
|
||||
new CMS.Views.Module(el: this).render()
|
||||
return @
|
||||
|
||||
edit: (event) ->
|
||||
event.preventDefault()
|
||||
CMS.replaceView(new CMS.Views.WeekEdit())
|
||||
|
||||
setHeight: =>
|
||||
@$el.height(@options.height)
|
||||
|
||||
resetHeight: =>
|
||||
@$el.height('')
|
||||
3
cms/static/coffee/src/views/week_edit.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
class CMS.Views.WeekEdit extends Backbone.View
|
||||
tagName: 'section'
|
||||
className: 'edit-pane'
|
||||
@@ -4,7 +4,7 @@ class @Unit
|
||||
|
||||
$("##{@element_id} .save-update").click (event) =>
|
||||
event.preventDefault()
|
||||
$.post("save_item", {
|
||||
$.post("/save_item", {
|
||||
id: @module_id
|
||||
data: JSON.stringify(@module.save())
|
||||
})
|
||||
|
||||
BIN
cms/static/img/content-types/chapter.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
cms/static/img/content-types/html.png
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
cms/static/img/content-types/lab.png
Normal file
|
After Width: | Height: | Size: 207 B |
BIN
cms/static/img/content-types/problem.png
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
cms/static/img/content-types/problemset.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
cms/static/img/content-types/sequential.png
Normal file
|
After Width: | Height: | Size: 122 B |
BIN
cms/static/img/content-types/vertical.png
Normal file
|
After Width: | Height: | Size: 144 B |
BIN
cms/static/img/content-types/video.png
Normal file
|
After Width: | Height: | Size: 129 B |
BIN
cms/static/img/content-types/videosequence.png
Normal file
|
After Width: | Height: | Size: 176 B |
38
cms/static/js/vendor/backbone-min.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Backbone.js 0.9.2
|
||||
|
||||
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
// Backbone may be freely distributed under the MIT license.
|
||||
// For all details and documentation:
|
||||
// http://backbonejs.org
|
||||
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
|
||||
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
|
||||
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
|
||||
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
|
||||
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
|
||||
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
|
||||
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
|
||||
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
|
||||
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
|
||||
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
|
||||
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
|
||||
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
|
||||
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
|
||||
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
|
||||
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
|
||||
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
|
||||
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
|
||||
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
|
||||
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
|
||||
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
|
||||
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
|
||||
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
|
||||
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
|
||||
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
|
||||
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
|
||||
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
|
||||
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
|
||||
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
|
||||
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
|
||||
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
|
||||
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
|
||||
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);
|
||||
366
cms/static/js/vendor/jasmine-jquery.js
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
var readFixtures = function() {
|
||||
return jasmine.getFixtures().proxyCallTo_('read', arguments)
|
||||
}
|
||||
|
||||
var preloadFixtures = function() {
|
||||
jasmine.getFixtures().proxyCallTo_('preload', arguments)
|
||||
}
|
||||
|
||||
var loadFixtures = function() {
|
||||
jasmine.getFixtures().proxyCallTo_('load', arguments)
|
||||
}
|
||||
|
||||
var appendLoadFixtures = function() {
|
||||
jasmine.getFixtures().proxyCallTo_('appendLoad', arguments)
|
||||
}
|
||||
|
||||
var setFixtures = function(html) {
|
||||
jasmine.getFixtures().proxyCallTo_('set', arguments)
|
||||
}
|
||||
|
||||
var appendSetFixtures = function() {
|
||||
jasmine.getFixtures().proxyCallTo_('appendSet', arguments)
|
||||
}
|
||||
|
||||
var sandbox = function(attributes) {
|
||||
return jasmine.getFixtures().sandbox(attributes)
|
||||
}
|
||||
|
||||
var spyOnEvent = function(selector, eventName) {
|
||||
jasmine.JQuery.events.spyOn(selector, eventName)
|
||||
}
|
||||
|
||||
jasmine.getFixtures = function() {
|
||||
return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures()
|
||||
}
|
||||
|
||||
jasmine.Fixtures = function() {
|
||||
this.containerId = 'jasmine-fixtures'
|
||||
this.fixturesCache_ = {}
|
||||
this.fixturesPath = 'spec/javascripts/fixtures'
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.set = function(html) {
|
||||
this.cleanUp()
|
||||
this.createContainer_(html)
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.appendSet= function(html) {
|
||||
this.addToContainer_(html)
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.preload = function() {
|
||||
this.read.apply(this, arguments)
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.load = function() {
|
||||
this.cleanUp()
|
||||
this.createContainer_(this.read.apply(this, arguments))
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.appendLoad = function() {
|
||||
this.addToContainer_(this.read.apply(this, arguments))
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.read = function() {
|
||||
var htmlChunks = []
|
||||
|
||||
var fixtureUrls = arguments
|
||||
for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
|
||||
htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]))
|
||||
}
|
||||
|
||||
return htmlChunks.join('')
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.clearCache = function() {
|
||||
this.fixturesCache_ = {}
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.cleanUp = function() {
|
||||
jQuery('#' + this.containerId).remove()
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.sandbox = function(attributes) {
|
||||
var attributesToSet = attributes || {}
|
||||
return jQuery('<div id="sandbox" />').attr(attributesToSet)
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.createContainer_ = function(html) {
|
||||
var container
|
||||
if(html instanceof jQuery) {
|
||||
container = jQuery('<div id="' + this.containerId + '" />')
|
||||
container.html(html)
|
||||
} else {
|
||||
container = '<div id="' + this.containerId + '">' + html + '</div>'
|
||||
}
|
||||
jQuery('body').append(container)
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.addToContainer_ = function(html){
|
||||
var container = jQuery('body').find('#'+this.containerId).append(html)
|
||||
if(!container.length){
|
||||
this.createContainer_(html)
|
||||
}
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
|
||||
if (typeof this.fixturesCache_[url] === 'undefined') {
|
||||
this.loadFixtureIntoCache_(url)
|
||||
}
|
||||
return this.fixturesCache_[url]
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
|
||||
var url = this.makeFixtureUrl_(relativeUrl)
|
||||
var request = new XMLHttpRequest()
|
||||
request.open("GET", url + "?" + new Date().getTime(), false)
|
||||
request.send(null)
|
||||
this.fixturesCache_[relativeUrl] = request.responseText
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.makeFixtureUrl_ = function(relativeUrl){
|
||||
return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
|
||||
}
|
||||
|
||||
jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
|
||||
return this[methodName].apply(this, passedArguments)
|
||||
}
|
||||
|
||||
|
||||
jasmine.JQuery = function() {}
|
||||
|
||||
jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
|
||||
return jQuery('<div/>').append(html).html()
|
||||
}
|
||||
|
||||
jasmine.JQuery.elementToString = function(element) {
|
||||
var domEl = $(element).get(0)
|
||||
if (domEl == undefined || domEl.cloneNode)
|
||||
return jQuery('<div />').append($(element).clone()).html()
|
||||
else
|
||||
return element.toString()
|
||||
}
|
||||
|
||||
jasmine.JQuery.matchersClass = {};
|
||||
|
||||
!function(namespace) {
|
||||
var data = {
|
||||
spiedEvents: {},
|
||||
handlers: []
|
||||
}
|
||||
|
||||
namespace.events = {
|
||||
spyOn: function(selector, eventName) {
|
||||
var handler = function(e) {
|
||||
data.spiedEvents[[selector, eventName]] = e
|
||||
}
|
||||
jQuery(selector).bind(eventName, handler)
|
||||
data.handlers.push(handler)
|
||||
},
|
||||
|
||||
wasTriggered: function(selector, eventName) {
|
||||
return !!(data.spiedEvents[[selector, eventName]])
|
||||
},
|
||||
|
||||
wasPrevented: function(selector, eventName) {
|
||||
return data.spiedEvents[[selector, eventName]].isDefaultPrevented()
|
||||
},
|
||||
|
||||
cleanUp: function() {
|
||||
data.spiedEvents = {}
|
||||
data.handlers = []
|
||||
}
|
||||
}
|
||||
}(jasmine.JQuery)
|
||||
|
||||
!function(){
|
||||
var jQueryMatchers = {
|
||||
toHaveClass: function(className) {
|
||||
return this.actual.hasClass(className)
|
||||
},
|
||||
|
||||
toHaveCss: function(css){
|
||||
for (var prop in css){
|
||||
if (this.actual.css(prop) !== css[prop]) return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
toBeVisible: function() {
|
||||
return this.actual.is(':visible')
|
||||
},
|
||||
|
||||
toBeHidden: function() {
|
||||
return this.actual.is(':hidden')
|
||||
},
|
||||
|
||||
toBeSelected: function() {
|
||||
return this.actual.is(':selected')
|
||||
},
|
||||
|
||||
toBeChecked: function() {
|
||||
return this.actual.is(':checked')
|
||||
},
|
||||
|
||||
toBeEmpty: function() {
|
||||
return this.actual.is(':empty')
|
||||
},
|
||||
|
||||
toExist: function() {
|
||||
return $(document).find(this.actual).length
|
||||
},
|
||||
|
||||
toHaveAttr: function(attributeName, expectedAttributeValue) {
|
||||
return hasProperty(this.actual.attr(attributeName), expectedAttributeValue)
|
||||
},
|
||||
|
||||
toHaveProp: function(propertyName, expectedPropertyValue) {
|
||||
return hasProperty(this.actual.prop(propertyName), expectedPropertyValue)
|
||||
},
|
||||
|
||||
toHaveId: function(id) {
|
||||
return this.actual.attr('id') == id
|
||||
},
|
||||
|
||||
toHaveHtml: function(html) {
|
||||
return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html)
|
||||
},
|
||||
|
||||
toContainHtml: function(html){
|
||||
var actualHtml = this.actual.html()
|
||||
var expectedHtml = jasmine.JQuery.browserTagCaseIndependentHtml(html)
|
||||
return (actualHtml.indexOf(expectedHtml) >= 0)
|
||||
},
|
||||
|
||||
toHaveText: function(text) {
|
||||
var trimmedText = $.trim(this.actual.text())
|
||||
if (text && jQuery.isFunction(text.test)) {
|
||||
return text.test(trimmedText)
|
||||
} else {
|
||||
return trimmedText == text
|
||||
}
|
||||
},
|
||||
|
||||
toHaveValue: function(value) {
|
||||
return this.actual.val() == value
|
||||
},
|
||||
|
||||
toHaveData: function(key, expectedValue) {
|
||||
return hasProperty(this.actual.data(key), expectedValue)
|
||||
},
|
||||
|
||||
toBe: function(selector) {
|
||||
return this.actual.is(selector)
|
||||
},
|
||||
|
||||
toContain: function(selector) {
|
||||
return this.actual.find(selector).length
|
||||
},
|
||||
|
||||
toBeDisabled: function(selector){
|
||||
return this.actual.is(':disabled')
|
||||
},
|
||||
|
||||
toBeFocused: function(selector) {
|
||||
return this.actual.is(':focus')
|
||||
},
|
||||
|
||||
toHandle: function(event) {
|
||||
|
||||
var events = this.actual.data('events')
|
||||
|
||||
if(!events || !event || typeof event !== "string") {
|
||||
return false
|
||||
}
|
||||
|
||||
var namespaces = event.split(".")
|
||||
var eventType = namespaces.shift()
|
||||
var sortedNamespaces = namespaces.slice(0).sort()
|
||||
var namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)")
|
||||
|
||||
if(events[eventType] && namespaces.length) {
|
||||
for(var i = 0; i < events[eventType].length; i++) {
|
||||
var namespace = events[eventType][i].namespace
|
||||
if(namespaceRegExp.test(namespace)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return events[eventType] && events[eventType].length > 0
|
||||
}
|
||||
},
|
||||
|
||||
// tests the existence of a specific event binding + handler
|
||||
toHandleWith: function(eventName, eventHandler) {
|
||||
var stack = this.actual.data("events")[eventName]
|
||||
for (var i = 0; i < stack.length; i++) {
|
||||
if (stack[i].handler == eventHandler) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var hasProperty = function(actualValue, expectedValue) {
|
||||
if (expectedValue === undefined) return actualValue !== undefined
|
||||
return actualValue == expectedValue
|
||||
}
|
||||
|
||||
var bindMatcher = function(methodName) {
|
||||
var builtInMatcher = jasmine.Matchers.prototype[methodName]
|
||||
|
||||
jasmine.JQuery.matchersClass[methodName] = function() {
|
||||
if (this.actual
|
||||
&& (this.actual instanceof jQuery
|
||||
|| jasmine.isDomNode(this.actual))) {
|
||||
this.actual = $(this.actual)
|
||||
var result = jQueryMatchers[methodName].apply(this, arguments)
|
||||
var element;
|
||||
if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML")
|
||||
this.actual = jasmine.JQuery.elementToString(this.actual)
|
||||
return result
|
||||
}
|
||||
|
||||
if (builtInMatcher) {
|
||||
return builtInMatcher.apply(this, arguments)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for(var methodName in jQueryMatchers) {
|
||||
bindMatcher(methodName)
|
||||
}
|
||||
}()
|
||||
|
||||
beforeEach(function() {
|
||||
this.addMatchers(jasmine.JQuery.matchersClass)
|
||||
this.addMatchers({
|
||||
toHaveBeenTriggeredOn: function(selector) {
|
||||
this.message = function() {
|
||||
return [
|
||||
"Expected event " + this.actual + " to have been triggered on " + selector,
|
||||
"Expected event " + this.actual + " not to have been triggered on " + selector
|
||||
]
|
||||
}
|
||||
return jasmine.JQuery.events.wasTriggered($(selector), this.actual)
|
||||
}
|
||||
})
|
||||
this.addMatchers({
|
||||
toHaveBeenPreventedOn: function(selector) {
|
||||
this.message = function() {
|
||||
return [
|
||||
"Expected event " + this.actual + " to have been prevented on " + selector,
|
||||
"Expected event " + this.actual + " not to have been prevented on " + selector
|
||||
]
|
||||
}
|
||||
return jasmine.JQuery.events.wasPrevented(selector, this.actual)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.getFixtures().cleanUp()
|
||||
jasmine.JQuery.events.cleanUp()
|
||||
})
|
||||
0
cms/static/js/jquery.inlineedit.js → cms/static/js/vendor/jquery.inlineedit.js
generated
vendored
487
cms/static/js/vendor/json2.js
vendored
Normal file
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
json2.js
|
||||
2011-10-19
|
||||
|
||||
Public Domain.
|
||||
|
||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
||||
|
||||
See http://www.JSON.org/js.html
|
||||
|
||||
|
||||
This code should be minified before deployment.
|
||||
See http://javascript.crockford.com/jsmin.html
|
||||
|
||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
||||
NOT CONTROL.
|
||||
|
||||
|
||||
This file creates a global JSON object containing two methods: stringify
|
||||
and parse.
|
||||
|
||||
JSON.stringify(value, replacer, space)
|
||||
value any JavaScript value, usually an object or array.
|
||||
|
||||
replacer an optional parameter that determines how object
|
||||
values are stringified for objects. It can be a
|
||||
function or an array of strings.
|
||||
|
||||
space an optional parameter that specifies the indentation
|
||||
of nested structures. If it is omitted, the text will
|
||||
be packed without extra whitespace. If it is a number,
|
||||
it will specify the number of spaces to indent at each
|
||||
level. If it is a string (such as '\t' or ' '),
|
||||
it contains the characters used to indent at each level.
|
||||
|
||||
This method produces a JSON text from a JavaScript value.
|
||||
|
||||
When an object value is found, if the object contains a toJSON
|
||||
method, its toJSON method will be called and the result will be
|
||||
stringified. A toJSON method does not serialize: it returns the
|
||||
value represented by the name/value pair that should be serialized,
|
||||
or undefined if nothing should be serialized. The toJSON method
|
||||
will be passed the key associated with the value, and this will be
|
||||
bound to the value
|
||||
|
||||
For example, this would serialize Dates as ISO strings.
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
You can provide an optional replacer method. It will be passed the
|
||||
key and value of each member, with this bound to the containing
|
||||
object. The value that is returned from your method will be
|
||||
serialized. If your method returns undefined, then the member will
|
||||
be excluded from the serialization.
|
||||
|
||||
If the replacer parameter is an array of strings, then it will be
|
||||
used to select the members to be serialized. It filters the results
|
||||
such that only members with keys listed in the replacer array are
|
||||
stringified.
|
||||
|
||||
Values that do not have JSON representations, such as undefined or
|
||||
functions, will not be serialized. Such values in objects will be
|
||||
dropped; in arrays they will be replaced with null. You can use
|
||||
a replacer function to replace those with JSON values.
|
||||
JSON.stringify(undefined) returns undefined.
|
||||
|
||||
The optional space parameter produces a stringification of the
|
||||
value that is filled with line breaks and indentation to make it
|
||||
easier to read.
|
||||
|
||||
If the space parameter is a non-empty string, then that string will
|
||||
be used for indentation. If the space parameter is a number, then
|
||||
the indentation will be that many spaces.
|
||||
|
||||
Example:
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
||||
// text is '["e",{"pluribus":"unum"}]'
|
||||
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
||||
|
||||
text = JSON.stringify([new Date()], function (key, value) {
|
||||
return this[key] instanceof Date ?
|
||||
'Date(' + this[key] + ')' : value;
|
||||
});
|
||||
// text is '["Date(---current time---)"]'
|
||||
|
||||
|
||||
JSON.parse(text, reviver)
|
||||
This method parses a JSON text to produce an object or array.
|
||||
It can throw a SyntaxError exception.
|
||||
|
||||
The optional reviver parameter is a function that can filter and
|
||||
transform the results. It receives each of the keys and values,
|
||||
and its return value is used instead of the original value.
|
||||
If it returns what it received, then the structure is not modified.
|
||||
If it returns undefined then the member is deleted.
|
||||
|
||||
Example:
|
||||
|
||||
// Parse the text. Values that look like ISO date strings will
|
||||
// be converted to Date objects.
|
||||
|
||||
myData = JSON.parse(text, function (key, value) {
|
||||
var a;
|
||||
if (typeof value === 'string') {
|
||||
a =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
||||
if (a) {
|
||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
||||
+a[5], +a[6]));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
||||
var d;
|
||||
if (typeof value === 'string' &&
|
||||
value.slice(0, 5) === 'Date(' &&
|
||||
value.slice(-1) === ')') {
|
||||
d = new Date(value.slice(5, -1));
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
|
||||
This is a reference implementation. You are free to copy, modify, or
|
||||
redistribute.
|
||||
*/
|
||||
|
||||
/*jslint evil: true, regexp: true */
|
||||
|
||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
||||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
|
||||
lastIndex, length, parse, prototype, push, replace, slice, stringify,
|
||||
test, toJSON, toString, valueOf
|
||||
*/
|
||||
|
||||
|
||||
// Create a JSON object only if one does not already exist. We create the
|
||||
// methods in a closure to avoid creating global variables.
|
||||
|
||||
var JSON;
|
||||
if (!JSON) {
|
||||
JSON = {};
|
||||
}
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
if (typeof Date.prototype.toJSON !== 'function') {
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
|
||||
return isFinite(this.valueOf())
|
||||
? this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z'
|
||||
: null;
|
||||
};
|
||||
|
||||
String.prototype.toJSON =
|
||||
Number.prototype.toJSON =
|
||||
Boolean.prototype.toJSON = function (key) {
|
||||
return this.valueOf();
|
||||
};
|
||||
}
|
||||
|
||||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
gap,
|
||||
indent,
|
||||
meta = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
},
|
||||
rep;
|
||||
|
||||
|
||||
function quote(string) {
|
||||
|
||||
// If the string contains no control characters, no quote characters, and no
|
||||
// backslash characters, then we can safely slap some quotes around it.
|
||||
// Otherwise we must also replace the offending characters with safe escape
|
||||
// sequences.
|
||||
|
||||
escapable.lastIndex = 0;
|
||||
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
||||
var c = meta[a];
|
||||
return typeof c === 'string'
|
||||
? c
|
||||
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
}) + '"' : '"' + string + '"';
|
||||
}
|
||||
|
||||
|
||||
function str(key, holder) {
|
||||
|
||||
// Produce a string from holder[key].
|
||||
|
||||
var i, // The loop counter.
|
||||
k, // The member key.
|
||||
v, // The member value.
|
||||
length,
|
||||
mind = gap,
|
||||
partial,
|
||||
value = holder[key];
|
||||
|
||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
||||
|
||||
if (value && typeof value === 'object' &&
|
||||
typeof value.toJSON === 'function') {
|
||||
value = value.toJSON(key);
|
||||
}
|
||||
|
||||
// If we were called with a replacer function, then call the replacer to
|
||||
// obtain a replacement value.
|
||||
|
||||
if (typeof rep === 'function') {
|
||||
value = rep.call(holder, key, value);
|
||||
}
|
||||
|
||||
// What happens next depends on the value's type.
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return quote(value);
|
||||
|
||||
case 'number':
|
||||
|
||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
|
||||
// If the value is a boolean or null, convert it to a string. Note:
|
||||
// typeof null does not produce 'null'. The case is included here in
|
||||
// the remote chance that this gets fixed someday.
|
||||
|
||||
return String(value);
|
||||
|
||||
// If the type is 'object', we might be dealing with an object or an array or
|
||||
// null.
|
||||
|
||||
case 'object':
|
||||
|
||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
||||
// so watch out for that case.
|
||||
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// Make an array to hold the partial results of stringifying this object value.
|
||||
|
||||
gap += indent;
|
||||
partial = [];
|
||||
|
||||
// Is the value an array?
|
||||
|
||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
||||
|
||||
// The value is an array. Stringify every element. Use null as a placeholder
|
||||
// for non-JSON values.
|
||||
|
||||
length = value.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
partial[i] = str(i, value) || 'null';
|
||||
}
|
||||
|
||||
// Join all of the elements together, separated with commas, and wrap them in
|
||||
// brackets.
|
||||
|
||||
v = partial.length === 0
|
||||
? '[]'
|
||||
: gap
|
||||
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
|
||||
: '[' + partial.join(',') + ']';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
|
||||
// If the replacer is an array, use it to select the members to be stringified.
|
||||
|
||||
if (rep && typeof rep === 'object') {
|
||||
length = rep.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
if (typeof rep[i] === 'string') {
|
||||
k = rep[i];
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Otherwise, iterate through all of the keys in the object.
|
||||
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all of the member texts together, separated with commas,
|
||||
// and wrap them in braces.
|
||||
|
||||
v = partial.length === 0
|
||||
? '{}'
|
||||
: gap
|
||||
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
|
||||
: '{' + partial.join(',') + '}';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// If the JSON object does not yet have a stringify method, give it one.
|
||||
|
||||
if (typeof JSON.stringify !== 'function') {
|
||||
JSON.stringify = function (value, replacer, space) {
|
||||
|
||||
// The stringify method takes a value and an optional replacer, and an optional
|
||||
// space parameter, and returns a JSON text. The replacer can be a function
|
||||
// that can replace values, or an array of strings that will select the keys.
|
||||
// A default replacer method can be provided. Use of the space parameter can
|
||||
// produce text that is more easily readable.
|
||||
|
||||
var i;
|
||||
gap = '';
|
||||
indent = '';
|
||||
|
||||
// If the space parameter is a number, make an indent string containing that
|
||||
// many spaces.
|
||||
|
||||
if (typeof space === 'number') {
|
||||
for (i = 0; i < space; i += 1) {
|
||||
indent += ' ';
|
||||
}
|
||||
|
||||
// If the space parameter is a string, it will be used as the indent string.
|
||||
|
||||
} else if (typeof space === 'string') {
|
||||
indent = space;
|
||||
}
|
||||
|
||||
// If there is a replacer, it must be a function or an array.
|
||||
// Otherwise, throw an error.
|
||||
|
||||
rep = replacer;
|
||||
if (replacer && typeof replacer !== 'function' &&
|
||||
(typeof replacer !== 'object' ||
|
||||
typeof replacer.length !== 'number')) {
|
||||
throw new Error('JSON.stringify');
|
||||
}
|
||||
|
||||
// Make a fake root object containing our value under the key of ''.
|
||||
// Return the result of stringifying the value.
|
||||
|
||||
return str('', {'': value});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// If the JSON object does not yet have a parse method, give it one.
|
||||
|
||||
if (typeof JSON.parse !== 'function') {
|
||||
JSON.parse = function (text, reviver) {
|
||||
|
||||
// The parse method takes a text and an optional reviver function, and returns
|
||||
// a JavaScript value if the text is a valid JSON text.
|
||||
|
||||
var j;
|
||||
|
||||
function walk(holder, key) {
|
||||
|
||||
// The walk method is used to recursively walk the resulting structure so
|
||||
// that modifications can be made.
|
||||
|
||||
var k, v, value = holder[key];
|
||||
if (value && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = walk(value, k);
|
||||
if (v !== undefined) {
|
||||
value[k] = v;
|
||||
} else {
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reviver.call(holder, key, value);
|
||||
}
|
||||
|
||||
|
||||
// Parsing happens in four stages. In the first stage, we replace certain
|
||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
||||
|
||||
text = String(text);
|
||||
cx.lastIndex = 0;
|
||||
if (cx.test(text)) {
|
||||
text = text.replace(cx, function (a) {
|
||||
return '\\u' +
|
||||
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
});
|
||||
}
|
||||
|
||||
// In the second stage, we run the text against regular expressions that look
|
||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
||||
// because they can cause invocation, and '=' because it can cause mutation.
|
||||
// But just to be safe, we want to reject all unexpected forms.
|
||||
|
||||
// We split the second stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
if (/^[\],:{}\s]*$/
|
||||
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
|
||||
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
|
||||
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
||||
|
||||
// In the third stage we use the eval function to compile the text into a
|
||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
||||
// in parens to eliminate the ambiguity.
|
||||
|
||||
j = eval('(' + text + ')');
|
||||
|
||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
||||
// each name/value pair to a reviver function for possible transformation.
|
||||
|
||||
return typeof reviver === 'function'
|
||||
? walk({'': j}, '')
|
||||
: j;
|
||||
}
|
||||
|
||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
||||
|
||||
throw new SyntaxError('JSON.parse');
|
||||
};
|
||||
}
|
||||
}());
|
||||
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 667 B |
|
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 859 B |
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 293 B After Width: | Height: | Size: 293 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 516 B After Width: | Height: | Size: 516 B |
|
Before Width: | Height: | Size: 223 B After Width: | Height: | Size: 223 B |
|
Before Width: | Height: | Size: 343 B After Width: | Height: | Size: 343 B |
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 357 B |
|
Before Width: | Height: | Size: 606 B After Width: | Height: | Size: 606 B |
|
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 537 B |
|
Before Width: | Height: | Size: 743 B After Width: | Height: | Size: 743 B |
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 957 B |
|
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 258 B |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 240 B |
32
cms/static/js/vendor/underscore-min.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Underscore.js 1.3.3
|
||||
// (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(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
|
||||
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
|
||||
g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
|
||||
c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
|
||||
a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
|
||||
c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
|
||||
a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
|
||||
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
|
||||
(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
|
||||
j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
|
||||
0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
|
||||
e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
|
||||
i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
|
||||
1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
|
||||
i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
|
||||
g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
|
||||
return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
|
||||
c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
|
||||
function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
|
||||
b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
|
||||
b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
|
||||
function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
|
||||
u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
|
||||
b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
|
||||
this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
|
||||
@@ -32,11 +32,11 @@ input {
|
||||
|
||||
button, input[type="submit"], .button {
|
||||
background-color: $orange;
|
||||
color: #fff;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding: 8px 10px;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
padding: 8px 10px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
&:hover {
|
||||
background-color: shade($orange, 10%);
|
||||
@@ -45,9 +45,22 @@ button, input[type="submit"], .button {
|
||||
|
||||
#{$all-text-inputs}, textarea {
|
||||
border: 1px solid $dark-blue;
|
||||
font: $body-font-size $body-font-family;
|
||||
padding: 4px 6px;
|
||||
@include box-shadow(inset 0 3px 6px $light-blue);
|
||||
color: lighten($dark-blue, 30%);
|
||||
font: $body-font-size $body-font-family;
|
||||
outline: none;
|
||||
padding: 4px 6px;
|
||||
|
||||
&:hover {
|
||||
background: lighten($yellow, 13%);
|
||||
color: $dark-blue;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include box-shadow(inset 0 3px 6px $light-blue, 0 0 3px lighten($bright-blue, 10%));
|
||||
color: $dark-blue;
|
||||
background: lighten($yellow, 13%);
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
@@ -56,7 +69,6 @@ textarea {
|
||||
line-height: lh();
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
// Extends
|
||||
@@ -87,18 +99,22 @@ textarea {
|
||||
}
|
||||
|
||||
.draggable {
|
||||
width: 7px;
|
||||
min-height: 14px;
|
||||
background: url('../img/drag-handle.png') no-repeat center;
|
||||
text-indent: -9999px;
|
||||
display: block;
|
||||
float: right;
|
||||
cursor: move;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
@include position(absolute, 0px 0px 0 0);
|
||||
width: 30px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.editable {
|
||||
&:hover {
|
||||
background: lighten($yellow, 10%);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 4px 10px;
|
||||
}
|
||||
@@ -107,13 +123,8 @@ textarea {
|
||||
.wip {
|
||||
outline: 1px solid #f00 !important;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: "WIP";
|
||||
font-size: 8px;
|
||||
padding: 2px;
|
||||
background: #f00;
|
||||
color: #fff;
|
||||
@include position(absolute, 0px 0px 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
section.cal {
|
||||
@include box-sizing(border-box);
|
||||
padding: 25px;
|
||||
@include clearfix;
|
||||
padding: 25px;
|
||||
|
||||
> header {
|
||||
@include clearfix;
|
||||
@@ -18,25 +18,44 @@ section.cal {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 14px;
|
||||
padding: 6px;
|
||||
padding: 6px 6px 6px 0;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include inline-block;
|
||||
float: right;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&.actions {
|
||||
float: left;
|
||||
}
|
||||
|
||||
li {
|
||||
@include inline-block;
|
||||
margin-left: 6px;
|
||||
border-left: 1px solid #ddd;
|
||||
padding: 0 6px;
|
||||
margin-right: 6px;
|
||||
border-right: 1px solid #ddd;
|
||||
padding: 0 6px 0 0;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
@include inline-block();
|
||||
font-size: 12px;
|
||||
@include inline-block;
|
||||
margin: 0 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
@@ -55,6 +74,8 @@ section.cal {
|
||||
border-top: 1px solid lighten($dark-blue, 40%);
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
@@ -62,8 +83,13 @@ section.cal {
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
width: flex-grid(3) + ((flex-gutter() * 3) / 4);
|
||||
background-color: lighten($light-blue, 2%);
|
||||
background-color: $light-blue;
|
||||
|
||||
&:hover {
|
||||
li.create-module {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
@@ -77,103 +103,133 @@ section.cal {
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
padding: 6px;
|
||||
color: $bright-blue;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
color: $bright-blue;
|
||||
display: block;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
|
||||
&:hover {
|
||||
color: darken($bright-blue, 10%);
|
||||
background: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
background: #fff;
|
||||
color: #888;
|
||||
border-bottom: 0;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin-bottom: 1px;
|
||||
margin: 0 0 1px 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
background: lighten($yellow, 10%);
|
||||
background-color: lighten($yellow, 14%);
|
||||
|
||||
a.draggable {
|
||||
background-color: lighten($yellow, 14%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.editable {
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: lighten($dark-blue, 10%);
|
||||
display: block;
|
||||
padding: 6px 35px 6px 6px;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
|
||||
&.draggable {
|
||||
background-color: $light-blue;
|
||||
opacity: .3;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.create-module {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
@include transition(all 3s ease-in-out);
|
||||
background: darken($light-blue, 2%);
|
||||
|
||||
> div {
|
||||
background: $dark-blue;
|
||||
@include box-shadow(0 0 5px darken($light-blue, 60%));
|
||||
@include box-sizing(border-box);
|
||||
display: none;
|
||||
margin-left: 3%;
|
||||
padding: 10px;
|
||||
@include position(absolute, 30px 0 0 0);
|
||||
width: 90%;
|
||||
background: rgba(#000, .9);
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
@include border-radius(3px);
|
||||
z-index: 99;
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: rgba(#000, .8);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@include position(absolute, -5px 0 0 50%);
|
||||
@include transform(rotate(45deg));
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
border-bottom: 0;
|
||||
background: none;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
border-color: #000;
|
||||
padding: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
|
||||
option {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $light-blue;
|
||||
float: right;
|
||||
|
||||
&:first-child {
|
||||
float: left;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
div {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +237,7 @@ section.cal {
|
||||
}
|
||||
|
||||
section.new-section {
|
||||
margin-top: 10px;
|
||||
margin: 10px 0 40px;
|
||||
@include inline-block();
|
||||
position: relative;
|
||||
|
||||
@@ -247,30 +303,48 @@ section.cal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.content
|
||||
section.cal {
|
||||
width: flex-grid(3) + flex-gutter();
|
||||
width: flex-grid(3);
|
||||
float: left;
|
||||
overflow: scroll;
|
||||
@include box-sizing(border-box);
|
||||
opacity: .4;
|
||||
@include transition();
|
||||
|
||||
> header ul {
|
||||
display: none;
|
||||
}
|
||||
background: darken($light-blue, 2%);
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
width: flex-grid(5) + flex-gutter();
|
||||
background-color: transparent;
|
||||
|
||||
+ section.main-content {
|
||||
width: flex-grid(7);
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
@include transition;
|
||||
overflow: hidden;
|
||||
|
||||
> a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
float: none;
|
||||
display: block;
|
||||
|
||||
li {
|
||||
ul {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
|
||||
55
cms/static/sass/_content-types.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
.content-type {
|
||||
padding-left: 34px;
|
||||
background-position: 8px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.videosequence a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/videosequence.png');
|
||||
}
|
||||
|
||||
.video a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/video.png');
|
||||
}
|
||||
|
||||
.problemset a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/problemset.png');
|
||||
}
|
||||
|
||||
.problem a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/problem.png');
|
||||
}
|
||||
|
||||
.lab a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/lab.png');
|
||||
}
|
||||
|
||||
.tab a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/lab.png');
|
||||
}
|
||||
|
||||
.html a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/html.png');
|
||||
}
|
||||
|
||||
.vertical a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/vertical.png');
|
||||
}
|
||||
|
||||
.sequential a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/sequential.png');
|
||||
}
|
||||
|
||||
.chapter a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/chapter.png');
|
||||
}
|
||||
@@ -5,6 +5,7 @@ body {
|
||||
|
||||
> section {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ body {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
margin: 0 15px 0 0;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
@@ -46,6 +47,7 @@ body {
|
||||
|
||||
ul {
|
||||
float: left;
|
||||
margin: 0;
|
||||
|
||||
&.user-nav {
|
||||
float: right;
|
||||
@@ -72,9 +74,10 @@ body {
|
||||
section.main-content {
|
||||
border-left: 2px solid $dark-blue;
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9);
|
||||
width: flex-grid(9) + flex-gutter();
|
||||
float: left;
|
||||
@include box-shadow( -2px 0 0 darken($light-blue, 3%));
|
||||
@include transition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
html, body, div, span, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
abbr, address, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, samp,
|
||||
small, strong, sub, sup, var,
|
||||
b, i,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin:0;
|
||||
padding:0;
|
||||
border:0;
|
||||
outline:0;
|
||||
vertical-align:baseline;
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
html,body {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
// Corrects block display not defined in IE8/9 & FF3
|
||||
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Corrects inline-block display not defined in IE8/9 & FF3
|
||||
audio, canvas, video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Prevents modern browsers from displaying 'audio' without controls
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Addresses styling for 'hidden' attribute not present in IE8/9, FF3, S4
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Prevents iOS text size adjust after orientation change, without disabling user zoom
|
||||
// www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
|
||||
html {
|
||||
font-size: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
// Addresses font-family inconsistency between 'textarea' and other form elements.
|
||||
html, button, input, select, textarea {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
// Addresses outline displayed oddly in Chrome
|
||||
&:focus {
|
||||
outline: thin dotted;
|
||||
// Webkit
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
// Improves readability when focused and also mouse hovered in all browsers
|
||||
// people.opera.com/patrickl/experiments/keyboard/test
|
||||
&:hover, &:active {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Addresses styling not present in IE8/9, S5, Chrome
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
// Addresses style set to 'bolder' in FF3+, S4/5, Chrome
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
// Addresses styling not present in S5, Chrome
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// Addresses styling not present in IE8/9
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
// Corrects font family set oddly in S4/5, Chrome
|
||||
// en.wikipedia.org/wiki/User:Davidgothberg/Test59
|
||||
pre, code, kbd, samp {
|
||||
font-family: monospace, serif;
|
||||
_font-family: 'courier new', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
// Improves readability of pre-formatted text in all browsers
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
// Addresses quote property not supported in S4
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
&:before, &:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
nav {
|
||||
ul, ol {
|
||||
list-style: none;
|
||||
list-style-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes border when inside 'a' element in IE8/9, FF3
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
// Corrects overflow displayed oddly in IE9
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Define consistent border, margin, and padding
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
legend {
|
||||
border: 0; // Corrects color not being inherited in IE8/9
|
||||
padding: 0;
|
||||
white-space: normal; // Corrects text not wrapping in FF3
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
font-size: 100%; // Corrects font size not being inherited in all browsers
|
||||
margin: 0; // Addresses margins set differently in FF3+, S5, Chrome
|
||||
vertical-align: baseline; // Improves appearance and consistency in all browsers
|
||||
}
|
||||
|
||||
// Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
|
||||
button, input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
button, input[type="button"], input[type="reset"], input[type="submit"] {
|
||||
cursor: pointer; // Improves usability and consistency of cursor style between image-type 'input' and others
|
||||
-webkit-appearance: button; // Corrects inability to style clickable 'input' types in iOS
|
||||
}
|
||||
|
||||
// Re-set default cursor for disabled elements
|
||||
button[disabled], input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
box-sizing: border-box; // Addresses box sizing set to content-box in IE8/9
|
||||
padding: 0; //Removes excess padding in IE8/9
|
||||
}
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; // Addresses appearance set to searchfield in S5, Chrome
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; // Addresses box-sizing set to border-box in S5, Chrome (-moz to future-proof)
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
// Removes inner padding and search cancel button in S5, Chrome on OS X
|
||||
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
// Removes inner padding and border in FF3+
|
||||
// www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
|
||||
button::-moz-focus-inner, input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto; // Removes default vertical scrollbar in IE8/9
|
||||
vertical-align: top; // Improves readability and alignment in all browsers
|
||||
}
|
||||
|
||||
// Remove most spacing between table cells
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -1,24 +1,48 @@
|
||||
section#unit-wrapper {
|
||||
section.filters {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
opacity: .4;
|
||||
margin-bottom: 10px;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include inline-block();
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 14px;
|
||||
padding: 6px 6px 6px 0;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include clearfix();
|
||||
list-style: none;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
@include inline-block;
|
||||
margin-right: 6px;
|
||||
border-right: 1px solid #ddd;
|
||||
padding-right: 6px;
|
||||
|
||||
&.advanced {
|
||||
&.search {
|
||||
float: right;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
&.more {
|
||||
font-size: 12px;
|
||||
@include inline-block;
|
||||
margin: 0 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,11 +63,13 @@ section#unit-wrapper {
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
color: $bright-blue;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 19px;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +92,8 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
@@ -76,15 +104,20 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -92,7 +125,7 @@ section#unit-wrapper {
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .5;
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
&.group {
|
||||
@@ -104,13 +137,15 @@ section#unit-wrapper {
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ol {
|
||||
border-left: 4px solid #999;
|
||||
border-bottom: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
&:last-child {
|
||||
@@ -133,31 +168,52 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
background: lighten($light-blue, 2%);
|
||||
background: $light-blue;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&.new-module a {
|
||||
background-color: darken($light-blue, 2%);
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-blue;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.empty {
|
||||
padding: 12px;
|
||||
|
||||
@@ -169,16 +225,10 @@ section#unit-wrapper {
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,19 @@ section#unit-wrapper {
|
||||
letter-spacing: 1px;
|
||||
@include inline-block();
|
||||
color: $bright-blue;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
text-indent: -9999px;
|
||||
@include inline-block();
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +46,7 @@ section#unit-wrapper {
|
||||
|
||||
&.save-update {
|
||||
@extend .button;
|
||||
margin: -6px -25px -6px 0;
|
||||
margin: -6px -21px -6px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,42 +56,68 @@ section#unit-wrapper {
|
||||
padding: 20px;
|
||||
|
||||
section.meta {
|
||||
background: $light-blue;
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
padding: 10px 20px;
|
||||
margin: -20px -20px 10px;
|
||||
opacity: .7;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
padding: 20px;
|
||||
margin: -20px -20px 10px;
|
||||
}
|
||||
|
||||
section {
|
||||
&.status-settings {
|
||||
float: left;
|
||||
margin-bottom: 10px;
|
||||
color: $dark-blue;
|
||||
@include clearfix;
|
||||
|
||||
ul {
|
||||
border: 1px solid darken($light-blue, 15%);
|
||||
@include clearfix();
|
||||
float: left;
|
||||
list-style: none;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
padding: 6px;
|
||||
border-right: 1px solid darken($light-blue, 15%);
|
||||
float: left;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-blue;
|
||||
padding: 6px;
|
||||
display: block;
|
||||
|
||||
&.current {
|
||||
background: darken($light-blue, 5%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 13%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.settings {
|
||||
@include inline-block();
|
||||
float: left;
|
||||
margin: 0 20px;
|
||||
padding: 6px;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
border: 1px solid darken($light-blue, 15%);
|
||||
color: $dark-blue;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 13%);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -110,10 +141,7 @@ section#unit-wrapper {
|
||||
}
|
||||
|
||||
&.tags {
|
||||
background: $light-blue;
|
||||
color: lighten($dark-blue, 6%);
|
||||
padding: 10px;
|
||||
margin: 0 0 20px;
|
||||
@include clearfix();
|
||||
clear: both;
|
||||
|
||||
@@ -124,10 +152,12 @@ section#unit-wrapper {
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,6 +222,8 @@ section#unit-wrapper {
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 20px;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@import 'bourbon/bourbon';
|
||||
@import 'reset';
|
||||
@import 'vendor/normalize';
|
||||
|
||||
@import 'base', 'layout';
|
||||
@import 'base', 'layout', 'content-types';
|
||||
@import 'calendar';
|
||||
@import 'section', 'unit';
|
||||
|
||||
504
cms/static/sass/vendor/_normalize.scss
vendored
Normal file
@@ -0,0 +1,504 @@
|
||||
/*! normalize.css 2012-03-11T12:53 UTC - http://github.com/necolas/normalize.css */
|
||||
|
||||
/* =============================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects inline-block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents modern browsers from displaying 'audio' without controls
|
||||
* Remove excess height in iOS5 devices
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4
|
||||
* Known issue: no IE6 support
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units
|
||||
* http://clagnut.com/blog/348/#c790
|
||||
* 2. Prevents iOS text size adjust after orientation change, without disabling user zoom
|
||||
* www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
|
||||
*/
|
||||
|
||||
html {
|
||||
font-size: 100%; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses font-family inconsistency between 'textarea' and other form elements.
|
||||
*/
|
||||
|
||||
html,
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins handled incorrectly in IE6/7
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses outline displayed oddly in Chrome
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability when focused and also mouse hovered in all browsers
|
||||
* people.opera.com/patrickl/experiments/keyboard/test
|
||||
*/
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses font sizes and margins set differently in IE6/7
|
||||
* Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
margin: 0.83em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.17em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
margin: 1.33em 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.83em;
|
||||
margin: 1.67em 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.75em;
|
||||
margin: 2.33em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE7/8/9, S5, Chrome
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses style set to 'bolder' in FF3+, S4/5, Chrome
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in S5, Chrome
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE6/7/8/9
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
p,
|
||||
pre {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects font family set oddly in IE6, S4/5, Chrome
|
||||
* en.wikipedia.org/wiki/User:Davidgothberg/Test59
|
||||
*/
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
_font-family: 'courier new', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability of pre-formatted text in all browsers
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses CSS quotes not supported in IE6/7
|
||||
* 2. Addresses quote property not supported in S4
|
||||
*/
|
||||
|
||||
/* 1 */
|
||||
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
/* 2 */
|
||||
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents sub and sup affecting line-height in all browsers
|
||||
* gist.github.com/413930
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Lists
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
dl,
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses paddings set differently in IE6/7
|
||||
*/
|
||||
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects list images handled incorrectly in IE7
|
||||
*/
|
||||
|
||||
nav ul,
|
||||
nav ol {
|
||||
list-style: none;
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Removes border when inside 'a' element in IE6/7/8/9, FF3
|
||||
* 2. Improves image quality when scaled in IE7
|
||||
* code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0; /* 1 */
|
||||
-ms-interpolation-mode: bicubic; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects overflow displayed oddly in IE9
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margin not present in IE6/7/8/9, S5, O11
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects margin displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Define consistent border, margin, and padding
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects color not being inherited in IE6/7/8/9
|
||||
* 2. Corrects text not wrapping in FF3
|
||||
* 3. Corrects alignment displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0;
|
||||
white-space: normal; /* 2 */
|
||||
*margin-left: -7px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects font size not being inherited in all browsers
|
||||
* 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome
|
||||
* 3. Improves appearance and consistency in all browsers
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-size: 100%; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
vertical-align: baseline; /* 3 */
|
||||
*vertical-align: middle; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal; /* 1 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Improves usability and consistency of cursor style between image-type 'input' and others
|
||||
* 2. Corrects inability to style clickable 'input' types in iOS
|
||||
* 3. Removes inner spacing in IE7 without affecting normal text inputs
|
||||
* Known issue: inner spacing remains in IE6
|
||||
*/
|
||||
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
cursor: pointer; /* 1 */
|
||||
-webkit-appearance: button; /* 2 */
|
||||
*overflow: visible; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-set default cursor for disabled elements
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses box sizing set to content-box in IE8/9
|
||||
* 2. Removes excess padding in IE8/9
|
||||
* 3. Removes excess padding in IE7
|
||||
Known issue: excess padding remains in IE6
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
*height: 13px; /* 3 */
|
||||
*width: 13px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses appearance set to searchfield in S5, Chrome
|
||||
* 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof)
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and search cancel button in S5, Chrome on OS X
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and border in FF3+
|
||||
* www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Removes default vertical scrollbar in IE6/7/8/9
|
||||
* 2. Improves readability and alignment in all browsers
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Remove most spacing between table cells
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -9,23 +9,26 @@
|
||||
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
|
||||
<%static:css group='base-style'/>
|
||||
% else:
|
||||
<link rel="stylesheet" href="${ STATIC_URL }/css/base-style.css">
|
||||
<link rel="stylesheet" href="${static.url('css/base-style.css')}">
|
||||
% endif
|
||||
<link rel="stylesheet" type="text/css" href="${ STATIC_URL }/js/markitup/skins/simple/style.css" />
|
||||
<link rel="stylesheet" type="text/css" href="${ STATIC_URL }/js/markitup/sets/wiki/style.css" />
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
|
||||
<title><%block name="title"></%block></title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<%include file="widgets/header.html"/>
|
||||
<%include file="widgets/header.html"/>
|
||||
|
||||
<%block name="content"></%block>
|
||||
<%block name="content"></%block>
|
||||
|
||||
<script type="text/javascript" src="${ STATIC_URL}/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="${ STATIC_URL }/js/markitup/jquery.markitup.js"></script>
|
||||
<script type="text/javascript" src="${ STATIC_URL }/js/markitup/sets/wiki/set.js"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/jquery.min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/markitup/jquery.markitup.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js"')}></script>
|
||||
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
|
||||
<%static:js group='main'/>
|
||||
% else:
|
||||
@@ -33,10 +36,10 @@
|
||||
% endif
|
||||
|
||||
<%static:js group='module-js'/>
|
||||
<script src="${ STATIC_URL }/js/jquery.inlineedit.js"></script>
|
||||
<script src="${ STATIC_URL }/js/jquery.cookie.js"></script>
|
||||
<script src="${ STATIC_URL }/js/jquery.leanModal.min.js"></script>
|
||||
<script src="${ STATIC_URL }/js/jquery.tablednd.js"></script>
|
||||
<script src="${static.url('js/vendor/jquery.inlineedit.js')}"></script>
|
||||
<script src="${static.url('js/vendor/jquery.cookie.js')}"></script>
|
||||
<script src="${static.url('js/vendor/jquery.leanModal.min.js')}"></script>
|
||||
<script src="${static.url('js/vendor/jquery.tablednd.js')}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
13
cms/templates/course_index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Course Manager</%block>
|
||||
|
||||
<%block name="content">
|
||||
<section class="main-container">
|
||||
|
||||
<%include file="widgets/navigation.html"/>
|
||||
|
||||
<section class="main-content">
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</%block>
|
||||
@@ -1,16 +1,12 @@
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Course Manager</%block>
|
||||
<%block name="title">Courses</%block>
|
||||
|
||||
<%block name="content">
|
||||
<section class="main-container">
|
||||
|
||||
<%include file="widgets/navigation.html"/>
|
||||
|
||||
<section class="main-content">
|
||||
<section class="edit-pane">
|
||||
<div id="module-html"/>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<ol>
|
||||
%for course, url in courses:
|
||||
<li><a href="${url}">${course}</a></li>
|
||||
%endfor
|
||||
</ol>
|
||||
</section>
|
||||
</%block>
|
||||
|
||||
66
cms/templates/jasmine/base.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Spec Runner</title>
|
||||
|
||||
{% load staticfiles %}
|
||||
<link rel="stylesheet" href="{% static 'jasmine-latest/jasmine.css' %}" media="screen">
|
||||
|
||||
{# core files #}
|
||||
<script src="{% static 'jasmine-latest/jasmine.js' %}"></script>
|
||||
<script src="{% static 'jasmine-latest/jasmine-html.js' %}"></script>
|
||||
<script src="{% static 'js/vendor/jasmine-jquery.js' %}"></script>
|
||||
|
||||
{# source files #}
|
||||
{% for url in suite.js_files %}
|
||||
<script src="{{ url }}"></script>
|
||||
{% endfor %}
|
||||
|
||||
{% load compressed %}
|
||||
{# static files #}
|
||||
{% compressed_js 'main' %}
|
||||
|
||||
{# spec files #}
|
||||
{% compressed_js 'spec' %}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Jasmine Spec Runner</h1>
|
||||
|
||||
<script>
|
||||
{% block jasmine %}
|
||||
(function() {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
|
||||
var trivialReporter = new jasmine.TrivialReporter();
|
||||
|
||||
jasmineEnv.addReporter(trivialReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return trivialReporter.specFilter(spec);
|
||||
};
|
||||
|
||||
// Additional configuration can be done in this block
|
||||
{% block jasmine_extra %}{% endblock %}
|
||||
|
||||
var currentWindowOnload = window.onload;
|
||||
|
||||
window.onload = function() {
|
||||
if (currentWindowOnload) {
|
||||
currentWindowOnload();
|
||||
}
|
||||
execJasmine();
|
||||
};
|
||||
|
||||
function execJasmine() {
|
||||
jasmineEnv.execute();
|
||||
}
|
||||
})();
|
||||
{% endblock %}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,7 +2,7 @@
|
||||
<header>
|
||||
<section>
|
||||
<h1 class="editable">${name}</h1>
|
||||
<p>${category}</p>
|
||||
<p class="${category}"><a href="#">${category}</a></p>
|
||||
</section>
|
||||
|
||||
<div class="actions">
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
<h2><a href="/">6.002x circuits and electronics</a></h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="new-module">New Section</a>
|
||||
<a href="#" class="new-module wip">New Module</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="new-module">New Unit</a>
|
||||
<a href="#" class="new-module wip">New Unit</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="user-nav">
|
||||
<li><a href="#">Tasks</a></li>
|
||||
<li><a href="#">Settings</a></li>
|
||||
<li><a href="#" class="wip">Tasks</a></li>
|
||||
<li><a href="#" class="wip">Settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<textarea name="" class="edit-box" rows="8" cols="40">${data}</textarea>
|
||||
<div class="preview">${data}</div>
|
||||
|
||||
<div class="actions wip">
|
||||
<div class="actions">
|
||||
<a href="" class="save-update">Save & Update</a>
|
||||
<a href="#" class="cancel">Cancel</a>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<a href="#" class="new-module">
|
||||
+ Add new module
|
||||
</a>
|
||||
<div>
|
||||
|
||||
<div class="hidden">
|
||||
<form>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1,44 +1,47 @@
|
||||
<section class="cal">
|
||||
<header class="wip">
|
||||
<h2>Filter content:</h2>
|
||||
<ul class="actions">
|
||||
<li><a href="#">Timeline view</a></li>
|
||||
<li><a href="#">Multi-Module edit</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#">Sequences</a>
|
||||
|
||||
<ul>
|
||||
<li>Hide all</li>
|
||||
<li>Lectures</li>
|
||||
<li>Labs</li>
|
||||
<li>Homeworks</li>
|
||||
<li>Exams</li>
|
||||
</ul>
|
||||
<h2>Sort:</h2>
|
||||
<select>
|
||||
<option value="">Linear Order</option>
|
||||
<option value="">Recently Modified</option>
|
||||
<option value="">Type</option>
|
||||
<option value="">Alphabetically</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="#">Deadlines</a>
|
||||
|
||||
<ul>
|
||||
<li>Today</li>
|
||||
<li>Tomorrow</li>
|
||||
<li>This week</li>
|
||||
<li>In 2 weeks</li>
|
||||
<li>This month</li>
|
||||
</ul>
|
||||
<h2>Filter:</h2>
|
||||
<select>
|
||||
<option value="">All content</option>
|
||||
<option value="">Videos</option>
|
||||
<option value="">Problems</option>
|
||||
<option value="">Labs</option>
|
||||
<option value="">Tutorials</option>
|
||||
<option value="">HTML</option>
|
||||
</select>
|
||||
<a href="#" class="more">More</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Goals</a>
|
||||
<ul>
|
||||
<li>Hide</li>
|
||||
</ul>
|
||||
<a href="#">Hide goals</a>
|
||||
</li>
|
||||
<li class="search">
|
||||
<input type="search" name="" id="" value="" placeholder="Search" />
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
|
||||
<ol>
|
||||
<ol id="weeks">
|
||||
% for week in weeks:
|
||||
<li>
|
||||
<li class="week" data-id="${week.location.url()}">
|
||||
<header>
|
||||
<h1><a href="#" class="module-edit" id="${week.location.url()}">${week.name}</a></h1>
|
||||
<h1><a href="#" class="week-edit">${week.name}</a></h1>
|
||||
<ul>
|
||||
% if 'goals' in week.metadata:
|
||||
% for goal in week.metadata['goals']:
|
||||
@@ -50,10 +53,10 @@
|
||||
</ul>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
<ul class="modules">
|
||||
% for module in week.get_children():
|
||||
<li class="${module.category}">
|
||||
<a href="#" class="module-edit" id="${module.location.url()}">${module.name}</a>
|
||||
<li class="module" data-id="${module.location.url()}" data-type="${module.js_module_name()}">
|
||||
<a href="#" class="module-edit">${module.name}</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
% endfor
|
||||
@@ -61,7 +64,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
%endfor
|
||||
<li>
|
||||
<li class="wip">
|
||||
<header>
|
||||
<h1>Course Scratch Pad</h1>
|
||||
</header>
|
||||
@@ -83,14 +86,15 @@
|
||||
<a href="" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<%include file="module-dropdown.html"/>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<section class="new-section">
|
||||
<a href="#" >+ Add New Section</a>
|
||||
<a href="#" class="wip" >+ Add New Section</a>
|
||||
|
||||
<section>
|
||||
<section class="hidden">
|
||||
<form>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
<section class="sequence-edit">
|
||||
<section class="filters">
|
||||
<section class="filters wip">
|
||||
<ul>
|
||||
<li>
|
||||
<label for="">Sort by</label>
|
||||
<h2>Sort:</h2>
|
||||
<select>
|
||||
<option value="">Linear Order</option>
|
||||
<option value="">Recently Modified</option>
|
||||
<option value="">Type</option>
|
||||
<option value="">Alphabetically</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="">Display</label>
|
||||
<h2>Filter:</h2>
|
||||
<select>
|
||||
<option value="">All content</option>
|
||||
<option value="">Videos</option>
|
||||
<option value="">Problems</option>
|
||||
<option value="">Labs</option>
|
||||
<option value="">Tutorials</option>
|
||||
<option value="">HTML</option>
|
||||
</select>
|
||||
<a href="#" class="more">More</a>
|
||||
</li>
|
||||
<li>
|
||||
<select>
|
||||
<option value="">Internal Only</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li class="advanced">
|
||||
<a href="#">Advanced filters</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="search" name="" id="" value="" />
|
||||
<li class="search">
|
||||
<input type="search" name="" id="" value="" placeholder="Search" />
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
@@ -36,8 +35,8 @@
|
||||
<li>
|
||||
<ol>
|
||||
% for child in module.get_children():
|
||||
<li>
|
||||
<a href="#" class="module-edit" id="${child.location.url()}">${child.name}</a>
|
||||
<li class="${module.category}">
|
||||
<a href="#" class="module-edit" data-id="${child.location.url()}" data-type="${child.js_module_name()}">${child.name}</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
%endfor
|
||||
@@ -47,7 +46,7 @@
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="scratch-pad">
|
||||
<section class="scratch-pad wip">
|
||||
<ol>
|
||||
<li class="new-module">
|
||||
<%include file="new-module.html"/>
|
||||
|
||||
15
cms/urls.py
@@ -1,12 +1,21 @@
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from django.conf import settings
|
||||
from django.conf.urls.defaults import patterns, include, url
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
# from django.contrib import admin
|
||||
# admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = ('',
|
||||
url(r'^$', 'contentstore.views.index', name='index'),
|
||||
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
|
||||
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
|
||||
url(r'^temp_force_export$', 'contentstore.views.temp_force_export')
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$', 'contentstore.views.course_index', name='course_index'),
|
||||
url(r'^temp_force_export$', 'contentstore.views.temp_force_export'),
|
||||
url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
## Jasmine
|
||||
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
|
||||
|
||||
urlpatterns = patterns(*urlpatterns)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
cd $(dirname $0) && django-admin.py collectstatic --noinput --settings=envs.aws --pythonpath=.
|
||||
16
common/djangoapps/util/json_request.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from functools import wraps
|
||||
import copy
|
||||
import json
|
||||
|
||||
def expect_json(view_function):
|
||||
@wraps(view_function)
|
||||
def expect_json_with_cloned_request(request, *args, **kwargs):
|
||||
if request.META['CONTENT_TYPE'] == "application/json":
|
||||
cloned_request = copy.copy(request)
|
||||
cloned_request.POST = cloned_request.POST.copy()
|
||||
cloned_request.POST.update(json.loads(request.raw_post_data))
|
||||
return view_function(cloned_request, *args, **kwargs)
|
||||
else:
|
||||
return view_function(request, *args, **kwargs)
|
||||
|
||||
return expect_json_with_cloned_request
|
||||
@@ -251,7 +251,7 @@ class LoncapaProblem(object):
|
||||
if file is not None:
|
||||
try:
|
||||
ifp = self.system.filestore.open(file) # open using I4xSystem OSFS filestore
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
|
||||
log.error('Cannot find file %s in %s' % (file,self.system.filestore))
|
||||
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
|
||||
@@ -259,7 +259,7 @@ class LoncapaProblem(object):
|
||||
else: continue
|
||||
try:
|
||||
incxml = etree.XML(ifp.read()) # read in and convert to XML
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
|
||||
log.error('Cannot parse XML in %s' % (file))
|
||||
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
|
||||
@@ -283,6 +283,7 @@ class LoncapaProblem(object):
|
||||
context.update(global_context) # initialize context to have stuff in global_context
|
||||
context['__builtins__'] = globals()['__builtins__'] # put globals there also
|
||||
context['the_lcp'] = self # pass instance of LoncapaProblem in
|
||||
context['script_code'] = ''
|
||||
|
||||
for script in tree.findall('.//script'):
|
||||
stype = script.get('type')
|
||||
@@ -295,10 +296,12 @@ class LoncapaProblem(object):
|
||||
code = script.text
|
||||
XMLESC = {"'": "'", """: '"'}
|
||||
code = unescape(code, XMLESC)
|
||||
context['script_code'] += code # store code source in context
|
||||
try:
|
||||
exec code in context, context # use "context" for global context; thus defs in code are global within code
|
||||
exec code in context, context # use "context" for global context; thus defs in code are global within code
|
||||
except Exception:
|
||||
log.exception("Error while execing code: " + code)
|
||||
log.exception("Error while execing script code: " + code)
|
||||
raise responsetypes.LoncapaProblemError("Error while executing script code")
|
||||
return context
|
||||
|
||||
def _extract_html(self, problemtree): # private
|
||||
@@ -311,6 +314,10 @@ class LoncapaProblem(object):
|
||||
|
||||
Used by get_html.
|
||||
'''
|
||||
if problemtree.tag=='script' and problemtree.get('type') and 'javascript' in problemtree.get('type'):
|
||||
# leave javascript intact.
|
||||
return problemtree
|
||||
|
||||
if problemtree.tag in html_problem_semantics:
|
||||
return
|
||||
|
||||