diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 2a9d48c35c..0000000000 --- a/Gemfile.lock +++ /dev/null @@ -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) diff --git a/cms/djangoapps/contentstore/__init__.py b/cms/djangoapps/contentstore/__init__.py index e69de29bb2..433278c47b 100644 --- a/cms/djangoapps/contentstore/__init__.py +++ b/cms/djangoapps/contentstore/__init__.py @@ -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 diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index f97ac10d41..89323c3d9b 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -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_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) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 9166763c87..8ac6aa610e 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1,21 +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): @@ -34,6 +49,14 @@ 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({})) diff --git a/cms/djangoapps/github_sync/__init__.py b/cms/djangoapps/github_sync/__init__.py new file mode 100644 index 0000000000..c901ad37e7 --- /dev/null +++ b/cms/djangoapps/github_sync/__init__.py @@ -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) + )) diff --git a/cms/djangoapps/github_sync/exceptions.py b/cms/djangoapps/github_sync/exceptions.py new file mode 100644 index 0000000000..9097ffc2a6 --- /dev/null +++ b/cms/djangoapps/github_sync/exceptions.py @@ -0,0 +1,2 @@ +class GithubSyncError(Exception): + pass diff --git a/cms/djangoapps/github_sync/tests/__init__.py b/cms/djangoapps/github_sync/tests/__init__.py new file mode 100644 index 0000000000..825fc68313 --- /dev/null +++ b/cms/djangoapps/github_sync/tests/__init__.py @@ -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')) diff --git a/cms/djangoapps/github_sync/tests/test_views.py b/cms/djangoapps/github_sync/tests/test_views.py new file mode 100644 index 0000000000..9e8095a67b --- /dev/null +++ b/cms/djangoapps/github_sync/tests/test_views.py @@ -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) + diff --git a/cms/djangoapps/github_sync/views.py b/cms/djangoapps/github_sync/views.py new file mode 100644 index 0000000000..8bf654f430 --- /dev/null +++ b/cms/djangoapps/github_sync/views.py @@ -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') diff --git a/cms/envs/common.py b/cms/envs/common.py index 1d036d761a..9782ef2fd0 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -31,6 +31,7 @@ from path import path MITX_FEATURES = { 'USE_DJANGO_PIPELINE': True, + 'GITHUB_PUSH': False, } ############################# SET PATH INFORMATION ############################# diff --git a/cms/envs/dev.py b/cms/envs/dev.py index b4bcbfa9ce..ca48481184 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -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': { diff --git a/cms/envs/test.py b/cms/envs/test.py index 032de92953..927e2af987 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -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 = { diff --git a/cms/static/coffee/unit.coffee b/cms/static/coffee/unit.coffee new file mode 100644 index 0000000000..dda41fe42b --- /dev/null +++ b/cms/static/coffee/unit.coffee @@ -0,0 +1,15 @@ +class @Unit + constructor: (@element_id, @module_id) -> + @module = new window[$("##{@element_id}").attr('class')] 'module-html' + + $("##{@element_id} .save-update").click (event) => + event.preventDefault() + $.post("/save_item", { + id: @module_id + data: JSON.stringify(@module.save()) + }) + + $("##{@element_id} .cancel").click (event) => + event.preventDefault() + CMS.edit_item(@module_id) + diff --git a/cms/static/img/content-types/chapter.png b/cms/static/img/content-types/chapter.png new file mode 100644 index 0000000000..2903f668d0 Binary files /dev/null and b/cms/static/img/content-types/chapter.png differ diff --git a/cms/static/img/content-types/html.png b/cms/static/img/content-types/html.png new file mode 100644 index 0000000000..566d0a232e Binary files /dev/null and b/cms/static/img/content-types/html.png differ diff --git a/cms/static/img/content-types/lab.png b/cms/static/img/content-types/lab.png new file mode 100644 index 0000000000..f5b457b8f2 Binary files /dev/null and b/cms/static/img/content-types/lab.png differ diff --git a/cms/static/img/content-types/problem.png b/cms/static/img/content-types/problem.png new file mode 100644 index 0000000000..198e57d629 Binary files /dev/null and b/cms/static/img/content-types/problem.png differ diff --git a/cms/static/img/content-types/problemset.png b/cms/static/img/content-types/problemset.png new file mode 100644 index 0000000000..85576db2ac Binary files /dev/null and b/cms/static/img/content-types/problemset.png differ diff --git a/cms/static/img/content-types/sequential.png b/cms/static/img/content-types/sequential.png new file mode 100644 index 0000000000..f0fda6d490 Binary files /dev/null and b/cms/static/img/content-types/sequential.png differ diff --git a/cms/static/img/content-types/vertical.png b/cms/static/img/content-types/vertical.png new file mode 100644 index 0000000000..17e4833f8b Binary files /dev/null and b/cms/static/img/content-types/vertical.png differ diff --git a/cms/static/img/content-types/video.png b/cms/static/img/content-types/video.png new file mode 100644 index 0000000000..1c9f0176bc Binary files /dev/null and b/cms/static/img/content-types/video.png differ diff --git a/cms/static/img/content-types/videosequence.png b/cms/static/img/content-types/videosequence.png new file mode 100644 index 0000000000..3a6c85843b Binary files /dev/null and b/cms/static/img/content-types/videosequence.png differ diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index b5156c0ec0..9cbc4ed048 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -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; } diff --git a/cms/static/sass/_calendar.scss b/cms/static/sass/_calendar.scss index 3dfda9c3d6..4070766617 100644 --- a/cms/static/sass/_calendar.scss +++ b/cms/static/sass/_calendar.scss @@ -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 { diff --git a/cms/static/sass/_content-types.scss b/cms/static/sass/_content-types.scss new file mode 100644 index 0000000000..6df113df78 --- /dev/null +++ b/cms/static/sass/_content-types.scss @@ -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'); +} diff --git a/cms/static/sass/_layout.scss b/cms/static/sass/_layout.scss index eb30d55f5c..f4c9f63ea6 100644 --- a/cms/static/sass/_layout.scss +++ b/cms/static/sass/_layout.scss @@ -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(); } } } diff --git a/cms/static/sass/_reset.scss b/cms/static/sass/_reset.scss deleted file mode 100644 index bfe619c1b0..0000000000 --- a/cms/static/sass/_reset.scss +++ /dev/null @@ -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; -} diff --git a/cms/static/sass/_section.scss b/cms/static/sass/_section.scss index f0ac33ba53..fa08e02901 100644 --- a/cms/static/sass/_section.scss +++ b/cms/static/sass/_section.scss @@ -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; - } } } - } } } diff --git a/cms/static/sass/_unit.scss b/cms/static/sass/_unit.scss index a21a4bf7a2..d8613be3c2 100644 --- a/cms/static/sass/_unit.scss +++ b/cms/static/sass/_unit.scss @@ -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; diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index 3a6c6e0cea..6d45331576 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -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'; diff --git a/cms/static/sass/vendor/_normalize.scss b/cms/static/sass/vendor/_normalize.scss new file mode 100644 index 0000000000..4474dee25a --- /dev/null +++ b/cms/static/sass/vendor/_normalize.scss @@ -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; +} diff --git a/cms/templates/course_index.html b/cms/templates/course_index.html new file mode 100644 index 0000000000..92b5cc296c --- /dev/null +++ b/cms/templates/course_index.html @@ -0,0 +1,13 @@ +<%inherit file="base.html" /> +<%block name="title">Course Manager + +<%block name="content"> +
+ + <%include file="widgets/navigation.html"/> + +
+
+ +
+ diff --git a/cms/templates/index.html b/cms/templates/index.html index 92b5cc296c..2998cb8bd6 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -1,13 +1,12 @@ <%inherit file="base.html" /> -<%block name="title">Course Manager +<%block name="title">Courses <%block name="content">
- - <%include file="widgets/navigation.html"/> - -
-
- +
    + %for course, url in courses: +
  1. ${course}
  2. + %endfor +
diff --git a/cms/templates/unit.html b/cms/templates/unit.html index 34e21ca049..cd921d2be2 100644 --- a/cms/templates/unit.html +++ b/cms/templates/unit.html @@ -2,7 +2,7 @@

${name}

-

${category}

+

${category}

diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 4577ac64d8..bad314560b 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -3,16 +3,16 @@

6.002x circuits and electronics

diff --git a/cms/templates/widgets/html-edit.html b/cms/templates/widgets/html-edit.html index f0f63ea905..47a6a55cba 100644 --- a/cms/templates/widgets/html-edit.html +++ b/cms/templates/widgets/html-edit.html @@ -36,7 +36,7 @@
${data}
-
+ diff --git a/cms/templates/widgets/module-dropdown.html b/cms/templates/widgets/module-dropdown.html index 6edb142e40..2fdd327cdd 100644 --- a/cms/templates/widgets/module-dropdown.html +++ b/cms/templates/widgets/module-dropdown.html @@ -2,7 +2,8 @@ + Add new module -
+ +