From f7c51870f3190ec141a04a58f2a6b5682f6a69e9 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Wed, 18 Jul 2012 12:00:42 -0400 Subject: [PATCH 001/352] initial setup for the discussion module --- common/lib/xmodule/setup.py | 1 + common/lib/xmodule/xmodule/discussion_module.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 common/lib/xmodule/xmodule/discussion_module.py diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index b495cd0aee..0495871e6a 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -34,6 +34,7 @@ setup( "video = xmodule.video_module:VideoDescriptor", "videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor", "videosequence = xmodule.seq_module:SequenceDescriptor", + "discussion = xmodule.discussion_module:DiscussionDescriptor", ] } ) diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py new file mode 100644 index 0000000000..475c41e04d --- /dev/null +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -0,0 +1,15 @@ +from xmodule.x_module import XModule +from xmodule.raw_module import RawDescriptor + + +class DiscussionModule(XModule): + def get_html(self): + return "Discussion: To be implemented" + + def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): + XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) + print "Initialized" + print definition + +class DiscussionDescriptor(RawDescriptor): + module_class = DiscussionModule From 6f74c9577d766ff6ebe627d415bad8e7c26b15e7 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 19 Jul 2012 14:46:08 -0400 Subject: [PATCH 002/352] display simple threaded comment --- .../lib/xmodule/xmodule/discussion_module.py | 18 +++++++++-- lms/lib/comment_client.py | 1 + lms/static/sass/_discussion.scss | 30 ++++++++++++++++++ lms/templates/discussion.html | 31 +++++++++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) create mode 120000 lms/lib/comment_client.py create mode 100644 lms/static/sass/_discussion.scss create mode 100644 lms/templates/discussion.html diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 475c41e04d..7cb94de1b3 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -1,15 +1,27 @@ +from lxml import etree + from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor +import comment_client +import dateutil +from dateutil.tz import tzlocal +from datehelper import time_ago_in_words class DiscussionModule(XModule): def get_html(self): - return "Discussion: To be implemented" + context = { + 'threads': comment_client.get_threads(self.discussion_id, recursive=True), + 'time_ago_in_words': time_ago_in_words, + 'parse': dateutil.parser.parse, + } + return self.system.render_template('discussion.html', context) def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) - print "Initialized" - print definition + xml_data = etree.fromstring(definition['data']) + self.discussion_id = xml_data.attrib['id'] + self.title = xml_data.attrib['for'] class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule diff --git a/lms/lib/comment_client.py b/lms/lib/comment_client.py new file mode 120000 index 0000000000..ecf4baf7e2 --- /dev/null +++ b/lms/lib/comment_client.py @@ -0,0 +1 @@ +/Users/dementrock/coding/cs_comments_client_python/comment_client.py \ No newline at end of file diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss new file mode 100644 index 0000000000..f2f40a307d --- /dev/null +++ b/lms/static/sass/_discussion.scss @@ -0,0 +1,30 @@ +.discussion { + .title { + font-size: 20px; + line-height: 20px; + font-weight: bold; + } + .thread { + margin-top: 10px; + margin-bottom: 10px; + .title { + font-size: 16px; + line-height: 16px; + } + .body { + font-size: 14px; + line-height: 14px; + } + .comments { + margin-left: 20px; + .comment { + .body { + } + .subcomments { + margin-left: 20px; + } + } + } + } + +} diff --git a/lms/templates/discussion.html b/lms/templates/discussion.html new file mode 100644 index 0000000000..07a2afe0c3 --- /dev/null +++ b/lms/templates/discussion.html @@ -0,0 +1,31 @@ +
+
+ Discussion +
+ % for thread in threads: +
+
${thread['title']}
+
${thread['body']}
+
+ ${time_ago_in_words(parse(thread['updated_at']))} ago by user No.${thread['user_id']} +
+
+ ${render_comments(thread['children'])} +
+
+ % endfor +
+ +<%def name="render_comments(comments)"> + % for comment in comments: +
+
${comment['body']}
+
+ ${time_ago_in_words(parse(comment['updated_at']))} ago by user No.${comment['user_id']} +
+
+ ${render_comments(comment['children'])} +
+
+ % endfor + From 2b89a82ac256502e70f52e7d38c349d702fb6639 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 19 Jul 2012 17:08:29 -0400 Subject: [PATCH 003/352] add discussion app & url conf --- lms/envs/common.py | 3 +++ lms/urls.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lms/envs/common.py b/lms/envs/common.py index ea07b8030e..f0c564fe4f 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -361,6 +361,9 @@ INSTALLED_APPS = ( # For testing 'django_jasmine', + # Discussion + 'django_comment_client', + # For Askbot 'django.contrib.sitemaps', 'django.contrib.admin', diff --git a/lms/urls.py b/lms/urls.py index 2cc1b88971..bd13e21084 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -154,6 +154,9 @@ if settings.ASKBOT_ENABLED: # url(r'^robots.txt$', include('robots.urls')), ) +# discussion +urlpatterns += (url(r'^discussions/', include('django_comment_client.urls')), ) + if settings.DEBUG: ## Jasmine urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),) From 3d43ba81ce47454083977e7f80dc060d585cfea9 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Fri, 20 Jul 2012 12:19:20 -0400 Subject: [PATCH 004/352] some basic functionalities of discussion --- lms/static/coffee/src/discussion.coffee | 135 ++++++++++++++++++++++++ lms/static/sass/_discussion.scss | 97 ++++++++++++++--- lms/static/sass/application.scss | 1 + lms/templates/discussion.html | 48 ++++++--- 4 files changed, 252 insertions(+), 29 deletions(-) create mode 100644 lms/static/coffee/src/discussion.coffee diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee new file mode 100644 index 0000000000..aaefa7deb4 --- /dev/null +++ b/lms/static/coffee/src/discussion.coffee @@ -0,0 +1,135 @@ +$ -> + + DEBUG = true + + $(".discussion-title").click -> + $thread = $(this).parent().children(".thread") + if $thread.css("display") == "none" + $thread.show() + else + $thread.hide() + + $(".thread-title").click -> + $comments = $(this).parent().parent().children(".comments") + if $comments.css("display") == "none" + $comments.show() + else + $comments.hide() + + getDiscussionContentLink = ($elem, selector) -> + $elem.children(".discussion-content-view").children(".info").children(selector) + + discussionContentHoverIn = -> + status = $(this).attr("status") || "normal" + if status == "normal" + getDiscussionContentLink($(this), ".discussion-link").show() + else if status == "reply" + getDiscussionContentLink($(this), ".discussion-cancel-reply").show() + getDiscussionContentLink($(this), ".discussion-submit-reply").show() + else if status == "edit" + getDiscussionContentLink($(this), ".discussion-cancel-edit").show() + getDiscussionContentLink($(this), ".discussion-update-edit").show() + + discussionContentHoverOut = -> + getDiscussionContentLink($(this), ".discussion-link").hide() + + $(".discussion-content").hover(discussionContentHoverIn, discussionContentHoverOut) + + $(".discussion-reply").click -> + handleReply(this) + + $(".discussion-cancel-reply").click -> + handleCancelReply(this) + + discussionLink = (cls, txt, handler) -> + $("").addClass("discussion-link"). + attr("href", "javascript:void(0)"). + addClass(cls).html(txt). + click(-> handler(this)) + + handleReply = (elem) -> + discussionContent = $(elem).parents(".discussion-content") + editView = discussionContent.children(".discussion-content-edit") + if editView.length + editView.show() + else + editView = $("
").addClass("discussion-content-edit") + editView.append($(" + New Post +
% for thread in threads:
From 784bd265106f1cb3ed2a0fef47ed93d94d81738c Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Sun, 22 Jul 2012 19:05:32 -0400 Subject: [PATCH 006/352] forum view for discussions --- .../lib/xmodule/xmodule/discussion_module.py | 3 +- lms/djangoapps/django_comment_client | 1 + lms/lib/datehelper | 1 + lms/static/coffee/src/discussion.coffee | 12 +++++- lms/static/sass/_discussion.scss | 7 ++++ lms/templates/course_navigation.html | 2 +- lms/templates/discussion/accordion.html | 26 +++++++++++++ lms/templates/discussion/index.html | 39 +++++++++++++++++++ .../inline.html} | 0 9 files changed, 88 insertions(+), 3 deletions(-) create mode 120000 lms/djangoapps/django_comment_client create mode 120000 lms/lib/datehelper create mode 100644 lms/templates/discussion/accordion.html create mode 100644 lms/templates/discussion/index.html rename lms/templates/{discussion.html => discussion/inline.html} (100%) diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 1def043503..3e9c133002 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -16,13 +16,14 @@ class DiscussionModule(XModule): 'parse': dateutil.parser.parse, 'commentable_id': self.discussion_id, } - return self.system.render_template('discussion.html', context) + return self.system.render_template('discussion/inline.html', context) def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) xml_data = etree.fromstring(definition['data']) self.discussion_id = xml_data.attrib['id'] self.title = xml_data.attrib['for'] + self.category = xml_data.attrib['category'] class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule diff --git a/lms/djangoapps/django_comment_client b/lms/djangoapps/django_comment_client new file mode 120000 index 0000000000..7ff5047e93 --- /dev/null +++ b/lms/djangoapps/django_comment_client @@ -0,0 +1 @@ +/Users/dementrock/coding/cs_comments_client_python/djangoapp \ No newline at end of file diff --git a/lms/lib/datehelper b/lms/lib/datehelper new file mode 120000 index 0000000000..bef380425d --- /dev/null +++ b/lms/lib/datehelper @@ -0,0 +1 @@ +/Users/dementrock/coding/datehelper \ No newline at end of file diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index fb8bfbef24..dbe3f14e89 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -3,6 +3,16 @@ $ -> #DEBUG = true DEBUG = false + if $('#accordion').length + active = $('#accordion ul:has(li.active)').index('#accordion ul') + $('#accordion').bind('accordionchange', @log).accordion + active: if active >= 0 then active else 1 + header: 'h3' + autoHeight: false + $('#open_close_accordion a').click @toggle + + $('#accordion').show() + $(".discussion-title").click -> $thread = $(this).parent().children(".thread") if $thread.css("display") == "none" @@ -103,7 +113,7 @@ $ -> return $edit = $div.children(".discussion-content").find(".comment-edit") body = $edit.val() - $.post url, {body: body}, handleAnchorAndReload(response, textStatus) -> + $.post url, {body: body}, (response, textStatus) -> if textStatus == "success" handleAnchorAndReload(response) console.log response diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index e8f2d2b951..270ad9f40c 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -16,6 +16,13 @@ $discussion_input_width: 60%; } } +.course-content { + .discussion { + margin-left: 40px; + margin-top: 15px; + } +} + .discussion { .discussion-title { @include discussion-font; diff --git a/lms/templates/course_navigation.html b/lms/templates/course_navigation.html index 8bda22148d..9ab9be598e 100644 --- a/lms/templates/course_navigation.html +++ b/lms/templates/course_navigation.html @@ -15,7 +15,7 @@ def url_class(url):
  • Course Info
  • % if user.is_authenticated():
  • Textbook
  • -
  • Discussion
  • +
  • Discussion
  • % endif
  • Wiki
  • % if user.is_authenticated(): diff --git a/lms/templates/discussion/accordion.html b/lms/templates/discussion/accordion.html new file mode 100644 index 0000000000..61cb3f6774 --- /dev/null +++ b/lms/templates/discussion/accordion.html @@ -0,0 +1,26 @@ +<%! from django.core.urlresolvers import reverse %> + +<% +def url_for(board): + if board['category'] == 'General': + return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id.replace('/', '-'), 'general']) + else: + return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id.replace('/', '-'), board['discussion_id']]) +%> + +<%def name="make_category(category, boards)"> +

    ${category}

    + + + + +% for category, boards in discussion_info.items(): + ${make_category(category, boards)} +% endfor diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html new file mode 100644 index 0000000000..85f611ee38 --- /dev/null +++ b/lms/templates/discussion/index.html @@ -0,0 +1,39 @@ +<%inherit file="../main.html" /> +<%namespace name='static' file='../static_content.html'/> +<%block name="bodyclass">courseware discussion +<%block name="title">Discussion – MITx 6.002x + +<%block name="headextra"> + + + +<%block name="js_extra"> + + + + +<%include file="../course_navigation.html" args="active_page='discussion'" /> + +
    +
    +
    +
    +

    Discussion Boards

    + close +
    + + +
    + +
    + ${content} +
    +
    +
    diff --git a/lms/templates/discussion.html b/lms/templates/discussion/inline.html similarity index 100% rename from lms/templates/discussion.html rename to lms/templates/discussion/inline.html From 616eaf25003244050c86695d99f1dc54b77ea55d Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 15:41:09 -0400 Subject: [PATCH 007/352] integrated search; refactored some frontend code --- .../lib/xmodule/xmodule/discussion_module.py | 5 +- lms/static/coffee/src/discussion.coffee | 224 +++++++++--------- lms/static/sass/_discussion.scss | 13 + lms/templates/course_navigation.html | 2 +- lms/templates/discussion/accordion.html | 5 +- lms/templates/discussion/index.html | 2 +- lms/templates/discussion/inline.html | 52 ++-- lms/templates/discussion/search_bar.html | 13 + lms/templates/discussion/single_thread.html | 61 +++++ 9 files changed, 242 insertions(+), 135 deletions(-) create mode 100644 lms/templates/discussion/search_bar.html create mode 100644 lms/templates/discussion/single_thread.html diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 3e9c133002..e1c41beeb6 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -11,10 +11,11 @@ from datehelper import time_ago_in_words class DiscussionModule(XModule): def get_html(self): context = { - 'threads': comment_client.get_threads(self.discussion_id, recursive=True), + 'threads': comment_client.get_threads(self.discussion_id, recursive=False), 'time_ago_in_words': time_ago_in_words, 'parse': dateutil.parser.parse, - 'commentable_id': self.discussion_id, + 'discussion_id': self.discussion_id, + 'search_bar': '', } return self.system.render_template('discussion/inline.html', context) diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index dbe3f14e89..7aea3507c7 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -1,8 +1,5 @@ $ -> - #DEBUG = true - DEBUG = false - if $('#accordion').length active = $('#accordion ul:has(li.active)').index('#accordion ul') $('#accordion').bind('accordionchange', @log).accordion @@ -10,84 +7,14 @@ $ -> header: 'h3' autoHeight: false $('#open_close_accordion a').click @toggle - $('#accordion').show() - $(".discussion-title").click -> - $thread = $(this).parent().children(".thread") - if $thread.css("display") == "none" - $thread.show() - else - $thread.hide() + $("section.discussion").each (index, discussion) -> + Discussion.bindDiscussionEvents(discussion) - $(".thread-title").click -> - $comments = $(this).parent().parent().children(".comments") - if $comments.css("display") == "none" - $comments.show() - else - $comments.hide() +Discussion = - getDiscussionContentLink = ($elem, selector) -> - $elem.children(".discussion-content-view").children(".info").children(selector) - - discussionContentHoverIn = -> - status = $(this).attr("status") || "normal" - if status == "normal" - getDiscussionContentLink($(this), ".discussion-link").show() - else if status == "reply" - getDiscussionContentLink($(this), ".discussion-cancel-reply").show() - getDiscussionContentLink($(this), ".discussion-submit-reply").show() - else if status == "edit" - getDiscussionContentLink($(this), ".discussion-cancel-edit").show() - getDiscussionContentLink($(this), ".discussion-update-edit").show() - - discussionContentHoverOut = -> - getDiscussionContentLink($(this), ".discussion-link").hide() - - $(".discussion-content").hover(discussionContentHoverIn, discussionContentHoverOut) - - $(".discussion-reply").click -> - handleReply(this) - - $(".discussion-cancel-reply").click -> - handleCancelReply(this) - - $(".discussion-new-post").click -> - handleSubmitNewThread(this) - - discussionLink = (cls, txt, handler) -> - $("").addClass("discussion-link"). - attr("href", "javascript:void(0)"). - addClass(cls).html(txt). - click(-> handler(this)) - - handleReply = (elem) -> - discussionContent = $(elem).parents(".discussion-content") - editView = discussionContent.children(".discussion-content-edit") - if editView.length - editView.show() - else - editView = $("
    ").addClass("discussion-content-edit") - editView.append($(" - New Post +
    + Discussion + ${search_bar} +
    + + + New Post +
    % for thread in threads: -
    -
    - ${thread['title']} -
    -
    ${thread['body']}
    -
    - ${render_info(thread)} + ${render_thread(thread)} + % endfor + + +<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> +
    +
    + ${thread['title']} +
    +
    ${thread['body']}
    +
    + ${render_info(thread)} + % if edit_thread: ${render_reply('')} ${render_edit('')} -
    + % endif
    +
    + % if show_comments:
    ${render_comments(thread['children'])}
    -
    - % endfor - + % endif +
    + <%def name="render_comments(comments)"> % for comment in comments: diff --git a/lms/templates/discussion/search_bar.html b/lms/templates/discussion/search_bar.html new file mode 100644 index 0000000000..dfc6ab5e2f --- /dev/null +++ b/lms/templates/discussion/search_bar.html @@ -0,0 +1,13 @@ +<%! from django.core.urlresolvers import reverse %> + +<% +def url_for_search(): + return reverse('django_comment_client.forum.views.search') +%> + +
    + + + + Search +
    diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html new file mode 100644 index 0000000000..4a009c10cb --- /dev/null +++ b/lms/templates/discussion/single_thread.html @@ -0,0 +1,61 @@ +<%! from django.core.urlresolvers import reverse %> + +
    + Discussion + ${render_thread(thread, edit_thread=True, show_comments=True)} +
    + +<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> +
    +
    + ${thread['title']} +
    +
    ${thread['body']}
    +
    + ${render_info(thread)} + % if edit_thread: + ${render_reply('')} + ${render_edit('')} + % endif +
    +
    +
    + % if show_comments: +
    + ${render_comments(thread['children'])} +
    + % endif +
    + + +<%def name="render_comments(comments)"> + % for comment in comments: +
    +
    +
    + ${comment['body']} +
    + ${render_info(comment)} + ${render_reply('')} + ${render_edit('')} +
    +
    +
    +
    + ${render_comments(comment['children'])} +
    +
    + % endfor + + +<%def name="render_info(content)"> + ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} + + +<%def name="render_reply(url)"> + Reply + + +<%def name="render_edit(url)"> + Edit + From fdab215eb4d37b6aa977a2ad4ef873abe45e0714 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 16:20:45 -0400 Subject: [PATCH 008/352] moved django_comment_client into edx --- lms/djangoapps/django_comment_client | 1 - .../django_comment_client/__init__.py | 0 .../django_comment_client/base/__init__.py | 0 .../django_comment_client/base/urls.py | 17 ++ .../django_comment_client/base/views.py | 141 +++++++++++++++ .../django_comment_client/forum/__init__.py | 0 .../django_comment_client/forum/urls.py | 8 + .../django_comment_client/forum/views.py | 168 ++++++++++++++++++ lms/djangoapps/django_comment_client/urls.py | 6 + 9 files changed, 340 insertions(+), 1 deletion(-) delete mode 120000 lms/djangoapps/django_comment_client create mode 100644 lms/djangoapps/django_comment_client/__init__.py create mode 100644 lms/djangoapps/django_comment_client/base/__init__.py create mode 100644 lms/djangoapps/django_comment_client/base/urls.py create mode 100644 lms/djangoapps/django_comment_client/base/views.py create mode 100644 lms/djangoapps/django_comment_client/forum/__init__.py create mode 100644 lms/djangoapps/django_comment_client/forum/urls.py create mode 100644 lms/djangoapps/django_comment_client/forum/views.py create mode 100644 lms/djangoapps/django_comment_client/urls.py diff --git a/lms/djangoapps/django_comment_client b/lms/djangoapps/django_comment_client deleted file mode 120000 index 7ff5047e93..0000000000 --- a/lms/djangoapps/django_comment_client +++ /dev/null @@ -1 +0,0 @@ -/Users/dementrock/coding/cs_comments_client_python/djangoapp \ No newline at end of file diff --git a/lms/djangoapps/django_comment_client/__init__.py b/lms/djangoapps/django_comment_client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/base/__init__.py b/lms/djangoapps/django_comment_client/base/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/base/urls.py b/lms/djangoapps/django_comment_client/base/urls.py new file mode 100644 index 0000000000..ec3ba7c625 --- /dev/null +++ b/lms/djangoapps/django_comment_client/base/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls.defaults import url, patterns +import django_comment_client.base.views + +urlpatterns = patterns('django_comment_client.base.views', + url(r'(?P[\w\-]+)/threads/create$', 'create_thread', name='create_thread'), + url(r'threads/(?P[\w\-]+)/update$', 'update_thread', name='update_thread'), + url(r'threads/(?P[\w\-]+)/reply$', 'create_comment', name='create_comment'), + url(r'threads/(?P[\w\-]+)/delete', 'delete_thread', name='delete_thread'), + url(r'comments/(?P[\w\-]+)/update$', 'update_comment', name='update_comment'), + url(r'comments/(?P[\w\-]+)/endorse$', 'endorse_comment', name='endorse_comment'), + url(r'comments/(?P[\w\-]+)/reply$', 'create_sub_comment', name='create_sub_comment'), + url(r'comments/(?P[\w\-]+)/delete$', 'delete_comment', name='delete_comment'), + url(r'comments/(?P[\w\-]+)/upvote$', 'vote_for_comment', {'value': 'up'}, name='upvote_comment'), + url(r'comments/(?P[\w\-]+)/downvote$', 'vote_for_comment', {'value': 'down'}, name='downvote_comment'), + url(r'threads/(?P[\w\-]+)/upvote$', 'vote_for_thread', {'value': 'up'}, name='upvote_thread'), + url(r'threads/(?P[\w\-]+)/downvote$', 'vote_for_thread', {'value': 'down'}, name='downvote_thread'), +) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py new file mode 100644 index 0000000000..291bb4c732 --- /dev/null +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -0,0 +1,141 @@ +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_POST, require_GET +from django.http import HttpResponse +from django.utils import simplejson + +import comment_client + +class JsonResponse(HttpResponse): + def __init__(self, data=None): + content = simplejson.dumps(data, + indent=2, + ensure_ascii=False) + super(JsonResponse, self).__init__(content, + mimetype='application/json; charset=utf8') + +class JsonError(HttpResponse): + def __init__(self, status, error_message=""): + content = simplejson.dumps({'errors': error_message}, + indent=2, + ensure_ascii=False) + super(JsonError, self).__init__(content, + status=status, + mimetype='application/json; charset=utf8') + +def thread_author_only(fn): + def verified_fn(request, *args, **kwargs): + thread_id = args.get('thread_id', False) or \ + kwargs.get('thread_id', False) + thread = comment_client.get_thread(thread_id) + if request.user.id == thread['user_id']: + return fn(request, *args, **kwargs) + else: + return JsonError(400, "unauthorized") + return verified_fn + +def comment_author_only(fn): + def verified_fn(request, *args, **kwargs): + comment_id = args.get('comment_id', False) or \ + kwargs.get('comment_id', False) + comment = comment_client.get_comment(comment_id) + if request.user.id == comment['user_id']: + return fn(request, *args, **kwargs) + else: + return JsonError(400, "unauthorized") + return verified_fn + +def instructor_only(fn): #TODO add instructor verification + return fn + +def extract(dic, keys): + return {k: dic[k] for k in keys} + +@login_required +@require_POST +def create_thread(request, commentable_id): + attributes = extract(request.POST, ['body', 'title']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_thread(commentable_id, attributes) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def update_thread(request, thread_id): + attributes = extract(request.POST, ['body', 'title']) + response = comment_client.update_thread(thread_id, attributes) + return JsonResponse(response) + +@login_required +@require_POST +def create_comment(request, thread_id): + attributes = extract(request.POST, ['body']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_comment(thread_id, attributes) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def delete_thread(request, thread_id): + response = comment_client.delete_thread(thread_id) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def update_comment(request, comment_id): + attributes = extract(request.POST, ['body']) + response = comment_client.update_comment(comment_id, attributes) + return JsonResponse(response) + +@instructor_only +@login_required +@require_POST +def endorse_comment(request, comment_id): + attributes = extract(request.POST, ['endorsed']) + response = comment_client.update_comment(comment_id, attributes) + return JsonResponse(response) + +@login_required +@require_POST +def create_sub_comment(request, comment_id): + attributes = extract(request.POST, ['body']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_sub_comment(comment_id, attributes) + return JsonResponse(response) + +@comment_author_only +@login_required +@require_POST +def delete_comment(request, comment_id): + response = comment_client.delete_comment(comment_id) + return JsonResponse(response) + +@login_required +@require_POST +def vote_for_comment(request, comment_id, value): + user_id = request.user.id + response = comment_client.vote_for_comment(comment_id, user_id, value) + return JsonResponse(response) + +@login_required +@require_POST +def vote_for_thread(request, thread_id, value): + user_id = request.user.id + response = comment_client.vote_for_thread(thread_id, user_id, value) + return JsonResponse(response) + +#undo vote: disabled for now + + +@login_required +@require_GET +def search(request): + text = request.GET.get('text', None) + commentable_id = request.GET.get('commentable_id', None) + response = comment_client.search(text, commentable_id) + return JsonResponse(response) diff --git a/lms/djangoapps/django_comment_client/forum/__init__.py b/lms/djangoapps/django_comment_client/forum/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/forum/urls.py b/lms/djangoapps/django_comment_client/forum/urls.py new file mode 100644 index 0000000000..3a724ebee4 --- /dev/null +++ b/lms/djangoapps/django_comment_client/forum/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import url, patterns +import django_comment_client.forum.views + +urlpatterns = patterns('django_comment_client.forum.views', + url(r'search$', 'search', name='search'), + url(r'threads/(?P\w+)$', 'single_thread', name='single_thread'), + url(r'(?P[\w\.\-]+)/(?P\w+)$', 'forum_form_discussion', name='forum_form_discussion'), +) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py new file mode 100644 index 0000000000..f6f893705e --- /dev/null +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -0,0 +1,168 @@ +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_POST +from django.http import HttpResponse +from django.utils import simplejson +from django.core.context_processors import csrf + +from mitxmako.shortcuts import render_to_response, render_to_string +from courseware.courses import check_course +from courseware.models import StudentModuleCache +from courseware.module_render import get_module, get_section +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore + +from importlib import import_module + +from django.conf import settings + +import comment_client +import dateutil +from dateutil.tz import tzlocal +from datehelper import time_ago_in_words + +import operator +import itertools + +_FULLMODULES = None +_DISCUSSIONINFO = None + +def get_full_modules(): + global _FULLMODULES + if not _FULLMODULES: + class_path = settings.MODULESTORE['default']['ENGINE'] + module_path, _, class_name = class_path.rpartition('.') + class_ = getattr(import_module(module_path), class_name) + modulestore = class_(eager=True, **settings.MODULESTORE['default']['OPTIONS']) + _FULLMODULES = modulestore.modules + return _FULLMODULES + +def get_categorized_discussion_info(request, user, course, course_name, url_course_id): + """ + return a dict of the form {category: modules} + """ + global _DISCUSSIONINFO + if not _DISCUSSIONINFO: + + _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ + and x[0].dict()['course'] == course_name + + _get_module_descriptor = operator.itemgetter(1) + + def _get_module(module_descriptor): + print module_descriptor + module = get_module(user, request, module_descriptor.location, student_module_cache)[0] + return module + + def _extract_info(module): + return { + 'title': module.title, + 'discussion_id': module.discussion_id, + 'category': module.category, + } + + discussion_module_descriptors = map(_get_module_descriptor, + filter(_is_course_discussion, + get_full_modules().items())) + + student_module_cache = StudentModuleCache(user, course) + + discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) + + _DISCUSSIONINFO = dict((category, list(l)) \ + for category, l in itertools.groupby(discussion_info, operator.itemgetter('category'))) + + _DISCUSSIONINFO['General'] = [{ + 'title': 'General discussion', + 'discussion_id': url_course_id, + 'category': 'General', + }] + + return _DISCUSSIONINFO + +def render_accordion(request, course, discussion_info, discussion_id): + context = dict([ + ('course', course), + ('discussion_info', discussion_info), + ('active', discussion_id), # TODO change this later + ('csrf', csrf(request)['csrf_token'])]) + + return render_to_string('discussion/accordion.html', context) + +def render_discussion(request, threads, discussion_id=None, search_text=''): + context = { + 'threads': threads, + 'time_ago_in_words': time_ago_in_words, + 'parse': dateutil.parser.parse, + 'discussion_id': discussion_id, + 'search_bar': render_search_bar(request, discussion_id, text=search_text), + } + return render_to_string('discussion/inline.html', context) + +def render_search_bar(request, discussion_id=None, text=''): + if not discussion_id: + return '' + context = { + 'discussion_id': discussion_id, + 'text': text, + } + return render_to_string('discussion/search_bar.html', context) + +def forum_form_discussion(request, course_id, discussion_id): + + course_id = course_id.replace('-', '/') + course = check_course(course_id) + + _, course_name, _ = course_id.split('/') + + url_course_id = course_id.replace('/', '_').replace('.', '_') + + discussion_info = get_categorized_discussion_info(request, request.user, course, course_name, url_course_id) + + search_text = request.GET.get('text', '') + + if len(search_text) > 0: + threads = comment_client.search(search_text, discussion_id) + else: + threads = comment_client.get_threads(discussion_id, recursive=False) + + context = { + 'csrf': csrf(request)['csrf_token'], + 'COURSE_TITLE': course.title, + 'course': course, + 'init': '', + 'content': render_discussion(request, threads, discussion_id, search_text), + 'accordion': render_accordion(request, course, discussion_info, discussion_id), + } + + return render_to_response('discussion/index.html', context) + +def render_single_thread(request, thread_id): + context = { + 'thread': comment_client.get_thread(thread_id, recursive=True), + 'time_ago_in_words': time_ago_in_words, + 'parse': dateutil.parser.parse, + } + return render_to_string('discussion/single_thread.html', context) + +def single_thread(request, thread_id): + + context = { + 'csrf': csrf(request)['csrf_token'], + 'init': '', + 'content': render_single_thread(request, thread_id), + 'accordion': '', + } + + return render_to_response('discussion/index.html', context) + +def search(request): + text = request.GET.get('text', None) + threads = comment_client.search(text) + context = { + 'csrf': csrf(request)['csrf_token'], + 'init': '', + 'content': render_discussion(request, threads, search_text=text), + 'accordion': '', + } + + return render_to_response('discussion/index.html', context) diff --git a/lms/djangoapps/django_comment_client/urls.py b/lms/djangoapps/django_comment_client/urls.py new file mode 100644 index 0000000000..959b5fa2ca --- /dev/null +++ b/lms/djangoapps/django_comment_client/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import url, patterns, include + +urlpatterns = patterns('', + url(r'forum/', include('django_comment_client.forum.urls')), + url(r'', include('django_comment_client.base.urls')), +) From d6be47d942c286622c236604a92e3da60d4f6550 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 19:03:36 -0400 Subject: [PATCH 009/352] refactored out thread rendering; vote --- lms/static/coffee/src/discussion.coffee | 17 +++- lms/static/sass/_discussion.scss | 39 ++++++++- lms/templates/discussion/inline.html | 66 +-------------- lms/templates/discussion/single_thread.html | 59 +------------- lms/templates/discussion/thread.html | 90 +++++++++++++++++++++ 5 files changed, 146 insertions(+), 125 deletions(-) create mode 100644 lms/templates/discussion/thread.html diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index 7aea3507c7..b3ee41203c 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -9,6 +9,7 @@ $ -> $('#open_close_accordion a').click @toggle $('#accordion').show() + $("section.discussion").each (index, discussion) -> Discussion.bindDiscussionEvents(discussion) @@ -32,7 +33,7 @@ Discussion = }[name] handleAnchorAndReload: (response) -> - window.location = window.location.pathname + "#" + response['id'] + #window.location = window.location.pathname + "#" + response['id'] window.location.reload() bindContentEvents: (content) -> @@ -100,12 +101,26 @@ Discussion = Discussion.handleAnchorAndReload(response) , 'json' + handleVote = (elem, value) -> + contentType = if $content.hasClass("thread") then "thread" else "comment" + url = Discussion.urlFor("#{value}vote_#{contentType}", $content.attr("_id")) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + $local(".discussion-reply").click -> handleReply(this) $local(".discussion-cancel-reply").click -> handleCancelReply(this) + $local(".discussion-vote-up").click -> + handleVote(this, "up") + + $local(".discussion-vote-down").click -> + handleVote(this, "down") + bindDiscussionEvents: (discussion) -> $discussion = $(discussion) $discussionNonContent = $discussion.children(".discussion-non-content") diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index b7820ff4a9..a5c2b5ed4f 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -1,4 +1,4 @@ -$comment_margin_left: 20px; +$comment_margin_left: 30px; $discussion_title_size: 1.6em; $comment_title_size: 1.2em; $comment_body_size: 1.0em; @@ -45,6 +45,33 @@ $discussion_input_width: 60%; margin-bottom: 20px; display: block; } + .discussion-votes { + margin-right: 8px; + margin-top: 5px; + text-align: center; + height: 40px; + float: left; + .discussion-vote-count { + font-size: $comment_body_size; + @include discussion-font; + } + a.discussion-vote { + display: block; + color: black; + font-weight: bold; + font-size: 15px; + &.discussion-vote-up { + margin-bottom: 3px; + } + &.discussion-vote-down { + margin-top: 3px; + } + } + } + .discussion-right-wrapper { + min-height: 40px; + float: left; + } .new-post-form { .new-post-title, .new-post-body { @include discussion-font; @@ -105,6 +132,8 @@ $discussion_input_width: 60%; } } .discussion-content { + margin-top: 10px; + overflow: hidden; .discussion-content-edit { .comment-edit { @include discussion-font; @@ -118,16 +147,20 @@ $discussion_input_width: 60%; .comments { //display: none; margin-left: $comment_margin_left; - + overflow: hidden; .comment { .comment-body { @include discussion-font; font-size: $comment_body_size; - margin-top: 10px; + margin-top: 3px; display: block; color: black; } } + .discussion-votes { + margin-right: 6px; + margin-top: 6px; + } } } diff --git a/lms/templates/discussion/inline.html b/lms/templates/discussion/inline.html index 91c905bf1a..e4f0ca43d3 100644 --- a/lms/templates/discussion/inline.html +++ b/lms/templates/discussion/inline.html @@ -1,11 +1,4 @@ -<%! from django.core.urlresolvers import reverse %> - -<% -def url_for(thread_id): - return reverse('django_comment_client.forum.views.single_thread', args=[thread_id]) -%> - - +<%namespace name="renderer" file="thread.html"/>
    @@ -18,61 +11,6 @@ def url_for(thread_id):
    % for thread in threads: - ${render_thread(thread)} + ${renderer.render_thread(thread, edit_thread=False, show_comments=False)} % endfor
    - -<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> -
    -
    - ${thread['title']} -
    -
    ${thread['body']}
    -
    - ${render_info(thread)} - % if edit_thread: - ${render_reply('')} - ${render_edit('')} - % endif -
    -
    -
    - % if show_comments: -
    - ${render_comments(thread['children'])} -
    - % endif -
    - - -<%def name="render_comments(comments)"> - % for comment in comments: -
    -
    -
    - ${comment['body']} -
    - ${render_info(comment)} - ${render_reply('')} - ${render_edit('')} -
    -
    -
    -
    - ${render_comments(comment['children'])} -
    -
    - % endfor - - -<%def name="render_info(content)"> - ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} - - -<%def name="render_reply(url)"> - Reply - - -<%def name="render_edit(url)"> - Edit - diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html index 4a009c10cb..28b17ff477 100644 --- a/lms/templates/discussion/single_thread.html +++ b/lms/templates/discussion/single_thread.html @@ -1,61 +1,6 @@ -<%! from django.core.urlresolvers import reverse %> +<%namespace name="renderer" file="thread.html"/>
    Discussion - ${render_thread(thread, edit_thread=True, show_comments=True)} + ${renderer.render_thread(thread, edit_thread=True, show_comments=True)}
    - -<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> -
    -
    - ${thread['title']} -
    -
    ${thread['body']}
    -
    - ${render_info(thread)} - % if edit_thread: - ${render_reply('')} - ${render_edit('')} - % endif -
    -
    -
    - % if show_comments: -
    - ${render_comments(thread['children'])} -
    - % endif -
    - - -<%def name="render_comments(comments)"> - % for comment in comments: -
    -
    -
    - ${comment['body']} -
    - ${render_info(comment)} - ${render_reply('')} - ${render_edit('')} -
    -
    -
    -
    - ${render_comments(comment['children'])} -
    -
    - % endfor - - -<%def name="render_info(content)"> - ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} - - -<%def name="render_reply(url)"> - Reply - - -<%def name="render_edit(url)"> - Edit - diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html new file mode 100644 index 0000000000..daaffb2939 --- /dev/null +++ b/lms/templates/discussion/thread.html @@ -0,0 +1,90 @@ +<%! from django.core.urlresolvers import reverse %> +<%! from datehelper import time_ago_in_words %> +<%! from dateutil.parser import parse %> + + + +<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> + <% + if show_comments: + url_for_thread = "" + else: + thread_id = thread['id'] + url_for_thread = reverse('django_comment_client.forum.views.single_thread', args=[thread_id]) + %> +
    +
    + ${render_vote(thread)} +
    + ${thread['title']} +
    +
    ${thread['body']}
    +
    + ${render_info(thread)} + % if edit_thread: + ${render_reply()} + ${render_edit()} + ${render_watch_thread()} + % endif +
    +
    +
    +
    + % if show_comments: +
    + ${render_comments(thread['children'])} +
    + % endif +
    + + +<%def name="render_comments(comments)"> + % for comment in comments: +
    +
    + ${render_vote(comment)} +
    +
    + ${comment['body']} +
    + ${render_info(comment)} + ${render_reply()} + ${render_edit()} +
    +
    +
    +
    +
    + ${render_comments(comment['children'])} +
    +
    + % endfor + + +<%def name="render_info(content)"> + ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} + + +<%def name="render_reply()"> + Reply + + +<%def name="render_edit()"> + Edit + + +<%def name="render_watch_thread()"> + Watch + + + +<%def name="render_vote(content)"> + <% + upvote = "˄" + downvote = "˅" + %> + + From 8c3931ba0a1a8137904cf0af533fcf12fb836fd8 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 19:24:28 -0400 Subject: [PATCH 010/352] removed unnecessary args --- lms/djangoapps/django_comment_client/forum/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index f6f893705e..e32fda2f5a 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -91,8 +91,6 @@ def render_accordion(request, course, discussion_info, discussion_id): def render_discussion(request, threads, discussion_id=None, search_text=''): context = { 'threads': threads, - 'time_ago_in_words': time_ago_in_words, - 'parse': dateutil.parser.parse, 'discussion_id': discussion_id, 'search_bar': render_search_bar(request, discussion_id, text=search_text), } @@ -139,8 +137,6 @@ def forum_form_discussion(request, course_id, discussion_id): def render_single_thread(request, thread_id): context = { 'thread': comment_client.get_thread(thread_id, recursive=True), - 'time_ago_in_words': time_ago_in_words, - 'parse': dateutil.parser.parse, } return render_to_string('discussion/single_thread.html', context) From a06cc229a012fa7d6b93027233e1628d565edb95 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 15:34:17 -0400 Subject: [PATCH 011/352] watch/unwatch threads; display current vote --- .../django_comment_client/base/urls.py | 13 ++- .../django_comment_client/base/views.py | 41 ++++++++- .../django_comment_client/forum/views.py | 13 ++- lms/static/coffee/src/discussion.coffee | 91 +++++++++++++++---- lms/static/sass/_discussion.scss | 3 + lms/templates/discussion/index.html | 5 +- lms/templates/discussion/thread.html | 3 - 7 files changed, 134 insertions(+), 35 deletions(-) diff --git a/lms/djangoapps/django_comment_client/base/urls.py b/lms/djangoapps/django_comment_client/base/urls.py index ec3ba7c625..a22ec401c3 100644 --- a/lms/djangoapps/django_comment_client/base/urls.py +++ b/lms/djangoapps/django_comment_client/base/urls.py @@ -2,16 +2,23 @@ from django.conf.urls.defaults import url, patterns import django_comment_client.base.views urlpatterns = patterns('django_comment_client.base.views', - url(r'(?P[\w\-]+)/threads/create$', 'create_thread', name='create_thread'), + url(r'threads/(?P[\w\-]+)/update$', 'update_thread', name='update_thread'), url(r'threads/(?P[\w\-]+)/reply$', 'create_comment', name='create_comment'), url(r'threads/(?P[\w\-]+)/delete', 'delete_thread', name='delete_thread'), + url(r'threads/(?P[\w\-]+)/upvote$', 'vote_for_thread', {'value': 'up'}, name='upvote_thread'), + url(r'threads/(?P[\w\-]+)/downvote$', 'vote_for_thread', {'value': 'down'}, name='downvote_thread'), + url(r'threads/(?P[\w\-]+)/watch$', 'watch_thread', name='watch_thread'), + url(r'threads/(?P[\w\-]+)/unwatch$', 'unwatch_thread', name='unwatch_thread'), + url(r'comments/(?P[\w\-]+)/update$', 'update_comment', name='update_comment'), url(r'comments/(?P[\w\-]+)/endorse$', 'endorse_comment', name='endorse_comment'), url(r'comments/(?P[\w\-]+)/reply$', 'create_sub_comment', name='create_sub_comment'), url(r'comments/(?P[\w\-]+)/delete$', 'delete_comment', name='delete_comment'), url(r'comments/(?P[\w\-]+)/upvote$', 'vote_for_comment', {'value': 'up'}, name='upvote_comment'), url(r'comments/(?P[\w\-]+)/downvote$', 'vote_for_comment', {'value': 'down'}, name='downvote_comment'), - url(r'threads/(?P[\w\-]+)/upvote$', 'vote_for_thread', {'value': 'up'}, name='upvote_thread'), - url(r'threads/(?P[\w\-]+)/downvote$', 'vote_for_thread', {'value': 'down'}, name='downvote_thread'), + + url(r'(?P[\w\-]+)/threads/create$', 'create_thread', name='create_thread'), + url(r'(?P[\w\-]+)/watch$', 'watch_commentable', name='watch_commentable'), + url(r'(?P[\w\-]+)/unwatch$', 'unwatch_commentable', name='unwatch_commentable'), ) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 291bb4c732..518751162d 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -129,8 +129,47 @@ def vote_for_thread(request, thread_id, value): response = comment_client.vote_for_thread(thread_id, user_id, value) return JsonResponse(response) -#undo vote: disabled for now +@login_required +@require_POST +def watch_thread(request, thread_id): + user_id = request.user.id + response = comment_client.subscribe_thread(user_id, thread_id) + return JsonResponse(response) +@login_required +@require_POST +def watch_commentable(request, commentable_id): + user_id = request.user.id + response = comment_client.subscribe_commentable(user_id, commentable_id) + return JsonResponse(response) + +@login_required +@require_POST +def follow(request, followed_user_id): + user_id = request.user.id + response = comment_client.follow(user_id, followed_user_id) + return JsonResponse(response) + +@login_required +@require_POST +def unwatch_thread(request, thread_id): + user_id = request.user.id + response = comment_client.unsubscribe_thread(user_id, thread_id) + return JsonResponse(response) + +@login_required +@require_POST +def unwatch_commentable(request, commentable_id): + user_id = request.user.id + response = comment_client.unsubscribe_commentable(user_id, commentable_id) + return JsonResponse(response) + +@login_required +@require_POST +def unfollow(request, followed_user_id): + user_id = request.user.id + response = comment_client.unfollow(user_id, followed_user_id) + return JsonResponse(response) @login_required @require_GET diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index e32fda2f5a..a7502bac49 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -22,6 +22,7 @@ from datehelper import time_ago_in_words import operator import itertools +import json _FULLMODULES = None _DISCUSSIONINFO = None @@ -80,11 +81,12 @@ def get_categorized_discussion_info(request, user, course, course_name, url_cour return _DISCUSSIONINFO def render_accordion(request, course, discussion_info, discussion_id): - context = dict([ - ('course', course), - ('discussion_info', discussion_info), - ('active', discussion_id), # TODO change this later - ('csrf', csrf(request)['csrf_token'])]) + context = { + 'course': course, + 'discussion_info': discussion_info, + 'active': discussion_id, + 'csrf': csrf(request)['csrf_token'], + } return render_to_string('discussion/accordion.html', context) @@ -147,6 +149,7 @@ def single_thread(request, thread_id): 'init': '', 'content': render_single_thread(request, thread_id), 'accordion': '', + 'user_info': json.dumps(comment_client.get_user_info(request.user.id)), } return render_to_response('discussion/index.html', context) diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index b3ee41203c..fe0007a8f0 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -9,38 +9,93 @@ $ -> $('#open_close_accordion a').click @toggle $('#accordion').show() - $("section.discussion").each (index, discussion) -> Discussion.bindDiscussionEvents(discussion) + Discussion.initializeDiscussion(discussion) + +generateLocal = (elem) -> + (selector) -> $(elem).find(selector) + +generateDiscussionLink = (cls, txt, handler) -> + $("").addClass("discussion-link"). + attr("href", "javascript:void(0)"). + addClass(cls).html(txt). + click(-> handler(this)) Discussion = urlFor: (name, param) -> { - create_thread : "/discussions/#{param}/threads/create" - update_thread : "/discussions/threads/#{param}/update" - create_comment : "/discussions/threads/#{param}/reply" - delete_thread : "/discussions/threads/#{param}/delete" - update_comment : "/discussions/comments/#{param}/update" - endorse_comment : "/discussions/comments/#{param}/endorse" - create_sub_comment : "/discussions/comments/#{param}/reply" - delete_comment : "/discussions/comments/#{param}/delete" - upvote_comment : "/discussions/comments/#{param}/upvote" - downvote_comment : "/discussions/comments/#{param}/downvote" - upvote_thread : "/discussions/threads/#{param}/upvote" - downvote_thread : "/discussions/threads/#{param}/downvote" - search : "/discussions/forum/search" + watch_commentable : "/discussions/#{param}/watch" + unwatch_commentable : "/discussions/#{param}/unwatch" + create_thread : "/discussions/#{param}/threads/create" + update_thread : "/discussions/threads/#{param}/update" + create_comment : "/discussions/threads/#{param}/reply" + delete_thread : "/discussions/threads/#{param}/delete" + upvote_thread : "/discussions/threads/#{param}/upvote" + downvote_thread : "/discussions/threads/#{param}/downvote" + watch_thread : "/discussions/threads/#{param}/watch" + unwatch_thread : "/discussions/threads/#{param}/unwatch" + update_comment : "/discussions/comments/#{param}/update" + endorse_comment : "/discussions/comments/#{param}/endorse" + create_sub_comment : "/discussions/comments/#{param}/reply" + delete_comment : "/discussions/comments/#{param}/delete" + upvote_comment : "/discussions/comments/#{param}/upvote" + downvote_comment : "/discussions/comments/#{param}/downvote" + search : "/discussions/forum/search" }[name] handleAnchorAndReload: (response) -> #window.location = window.location.pathname + "#" + response['id'] window.location.reload() + initializeDiscussion: (discussion) -> + initializeVote = (index, content) -> + $content = $(content) + $local = generateLocal($content.children(".discussion-content")) + id = $content.attr("_id") + if id in user_info.upvoted_ids + $local(".discussion-vote-up").addClass("voted") + else if id in user_info.downvoted_ids + $local(".discussion-vote-down").addClass("voted") + + + initializeWatchThreads = (index, thread) -> + $thread = $(thread) + id = $thread.attr("_id") + $local = generateLocal($thread.children(".discussion-content")) + + handleWatchThread = (elem) -> + url = Discussion.urlFor('watch_thread', id) + console.log url + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + handleUnwatchThread = (elem) -> + url = Discussion.urlFor('unwatch_thread', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + if id in user_info.subscribed_thread_ids + unwatchThread = generateDiscussionLink("discussion-unwatch-thread", "Unwatch", handleUnwatchThread) + $local(".info").append(unwatchThread) + else + watchThread = generateDiscussionLink("discussion-watch-thread", "Watch", handleWatchThread) + $local(".info").append(watchThread) + + if user_info? + $(discussion).find(".comment").each(initializeVote) + $(discussion).find(".thread").each(initializeVote).each(initializeWatchThreads) + bindContentEvents: (content) -> $content = $(content) $discussionContent = $content.children(".discussion-content") - $local = (selector) -> $discussionContent.find(selector) + $local = generateLocal($discussionContent) discussionContentHoverIn = -> status = $discussionContent.attr("status") || "normal" @@ -58,11 +113,7 @@ Discussion = $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) - generateDiscussionLink = (cls, txt, handler) -> - $("").addClass("discussion-link"). - attr("href", "javascript:void(0)"). - addClass(cls).html(txt). - click(-> handler(this)) + handleReply = (elem) -> editView = $local(".discussion-content-edit") diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index a5c2b5ed4f..724d7b15cf 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -66,6 +66,9 @@ $discussion_input_width: 60%; &.discussion-vote-down { margin-top: 3px; } + &.voted { + color: #1d9dd9; + } } } .discussion-right-wrapper { diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index d150dc7ddf..c970bfbcf4 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -8,11 +8,10 @@ <%block name="js_extra"> - + ##<%include file="../course_navigation.html" args="active_page='discussion'" /> diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html index daaffb2939..f76d3de8df 100644 --- a/lms/templates/discussion/thread.html +++ b/lms/templates/discussion/thread.html @@ -2,8 +2,6 @@ <%! from datehelper import time_ago_in_words %> <%! from dateutil.parser import parse %> - - <%def name="render_thread(thread, edit_thread=False, show_comments=False)"> <% if show_comments: @@ -24,7 +22,6 @@ % if edit_thread: ${render_reply()} ${render_edit()} - ${render_watch_thread()} % endif
    From 856156a14138aa06aa497316891997cb2e376565 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 16:49:27 -0400 Subject: [PATCH 012/352] provide user_id for xmodules --- lms/djangoapps/courseware/module_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 00ffb8608b..4582627813 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -208,6 +208,7 @@ def get_module(user, request, location, student_module_cache, position=None): shared_module = None instance_state = instance_module.state if instance_module is not None else None + instance_state = json.dumps(dict(json.loads(instance_state).items() + [("user_id", user.id)])) shared_state = shared_module.state if shared_module is not None else None # Setup system context for module instance From 577599d50567cb5deba09f9b17219a9350077c48 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 16:49:53 -0400 Subject: [PATCH 013/352] get user_id in discussion module --- common/lib/xmodule/xmodule/discussion_module.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index e1c41beeb6..ffc0597b19 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -8,6 +8,8 @@ import dateutil from dateutil.tz import tzlocal from datehelper import time_ago_in_words +import json + class DiscussionModule(XModule): def get_html(self): context = { @@ -16,15 +18,20 @@ class DiscussionModule(XModule): 'parse': dateutil.parser.parse, 'discussion_id': self.discussion_id, 'search_bar': '', + 'user_info': comment_client.get_user_info(self.user_id, raw=True), } return self.system.render_template('discussion/inline.html', context) def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) + + if isinstance(instance_state, str): + instance_state = json.loads(instance_state) xml_data = etree.fromstring(definition['data']) self.discussion_id = xml_data.attrib['id'] self.title = xml_data.attrib['for'] self.category = xml_data.attrib['category'] + self.user_id = instance_state['user_id'] class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule From f485167bd4328cdfeb064196a5f711653a311363 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 16:50:21 -0400 Subject: [PATCH 014/352] watch/unwatch, vote status enabled in all views --- lms/djangoapps/django_comment_client/forum/views.py | 4 +++- lms/templates/discussion/index.html | 4 ---- lms/templates/discussion/inline.html | 5 +++++ lms/templates/discussion/single_thread.html | 5 +++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index a7502bac49..dfff24e5bf 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -95,6 +95,7 @@ def render_discussion(request, threads, discussion_id=None, search_text=''): 'threads': threads, 'discussion_id': discussion_id, 'search_bar': render_search_bar(request, discussion_id, text=search_text), + 'user_info': comment_client.get_user_info(request.user.id, raw=True), } return render_to_string('discussion/inline.html', context) @@ -139,6 +140,7 @@ def forum_form_discussion(request, course_id, discussion_id): def render_single_thread(request, thread_id): context = { 'thread': comment_client.get_thread(thread_id, recursive=True), + 'user_info': comment_client.get_user_info(request.user.id, raw=True), } return render_to_string('discussion/single_thread.html', context) @@ -149,7 +151,7 @@ def single_thread(request, thread_id): 'init': '', 'content': render_single_thread(request, thread_id), 'accordion': '', - 'user_info': json.dumps(comment_client.get_user_info(request.user.id)), + 'user_info': comment_client.get_user_info(request.user.id, raw=True), } return render_to_response('discussion/index.html', context) diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index c970bfbcf4..7b35863377 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -8,10 +8,6 @@ <%block name="js_extra"> - - ##<%include file="../course_navigation.html" args="active_page='discussion'" /> diff --git a/lms/templates/discussion/inline.html b/lms/templates/discussion/inline.html index e4f0ca43d3..d9d5bb8df1 100644 --- a/lms/templates/discussion/inline.html +++ b/lms/templates/discussion/inline.html @@ -14,3 +14,8 @@ ${renderer.render_thread(thread, edit_thread=False, show_comments=False)} % endfor + + + diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html index 28b17ff477..0545b2ba4a 100644 --- a/lms/templates/discussion/single_thread.html +++ b/lms/templates/discussion/single_thread.html @@ -4,3 +4,8 @@ Discussion ${renderer.render_thread(thread, edit_thread=True, show_comments=True)} + + + From bedeac3a557498dd9f5c618983619cc538adf26c Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 18:51:56 -0400 Subject: [PATCH 015/352] make course id available in xmodule --- .../lib/xmodule/xmodule/discussion_module.py | 4 +- lms/djangoapps/courseware/module_render.py | 15 +++- .../django_comment_client/base/views.py | 38 +++++----- .../django_comment_client/forum/urls.py | 2 +- .../django_comment_client/forum/views.py | 30 +++++--- lms/static/coffee/src/discussion.coffee | 69 +++++++++++++------ lms/static/sass/_discussion.scss | 17 +++++ lms/templates/course_navigation.html | 2 +- lms/templates/discussion/accordion.html | 18 ++--- lms/templates/discussion/index.html | 2 +- lms/templates/discussion/inline.html | 12 ++-- lms/templates/discussion/search_bar.html | 2 +- lms/templates/discussion/single_thread.html | 6 +- lms/templates/discussion/thread.html | 4 +- lms/urls.py | 6 +- 15 files changed, 147 insertions(+), 80 deletions(-) diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index ffc0597b19..24ba4ad206 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -19,6 +19,7 @@ class DiscussionModule(XModule): 'discussion_id': self.discussion_id, 'search_bar': '', 'user_info': comment_client.get_user_info(self.user_id, raw=True), + 'course_id': self.course_id, } return self.system.render_template('discussion/inline.html', context) @@ -30,8 +31,9 @@ class DiscussionModule(XModule): xml_data = etree.fromstring(definition['data']) self.discussion_id = xml_data.attrib['id'] self.title = xml_data.attrib['for'] - self.category = xml_data.attrib['category'] + self.discussion_category = xml_data.attrib['discussion_category'] self.user_id = instance_state['user_id'] + self.course_id = instance_state['course_id'] class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 4582627813..8e89f79a01 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -200,6 +200,13 @@ def get_module(user, request, location, student_module_cache, position=None): ''' descriptor = modulestore().get_item(location) + user_id = user.id + + import re + course_id = re.search(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/', request.path) + if course_id: + course_id = course_id.group('course_id') + instance_module = student_module_cache.lookup(descriptor.category, descriptor.location.url()) shared_state_key = getattr(descriptor, 'shared_state_key', None) if shared_state_key is not None: @@ -207,8 +214,12 @@ def get_module(user, request, location, student_module_cache, position=None): else: shared_module = None - instance_state = instance_module.state if instance_module is not None else None - instance_state = json.dumps(dict(json.loads(instance_state).items() + [("user_id", user.id)])) + instance_state = instance_module.state if instance_module is not None else {} + + instance_hash = json.loads(instance_state) if isinstance(instance_state, str) or isinstance(instance_state, unicode) \ + else instance_state + + instance_state = json.dumps(dict(instance_hash.items() + [("user_id", user.id), ("course_id", course_id)])) shared_state = shared_module.state if shared_module is not None else None # Setup system context for module instance diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 518751162d..a148d80020 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -52,41 +52,41 @@ def extract(dic, keys): @login_required @require_POST -def create_thread(request, commentable_id): +def create_thread(request, course_id, commentable_id): attributes = extract(request.POST, ['body', 'title']) attributes['user_id'] = request.user.id - attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + attributes['course_id'] = course_id response = comment_client.create_thread(commentable_id, attributes) return JsonResponse(response) @thread_author_only @login_required @require_POST -def update_thread(request, thread_id): +def update_thread(request, course_id, thread_id): attributes = extract(request.POST, ['body', 'title']) response = comment_client.update_thread(thread_id, attributes) return JsonResponse(response) @login_required @require_POST -def create_comment(request, thread_id): +def create_comment(request, course_id, thread_id): attributes = extract(request.POST, ['body']) attributes['user_id'] = request.user.id - attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + attributes['course_id'] = course_id response = comment_client.create_comment(thread_id, attributes) return JsonResponse(response) @thread_author_only @login_required @require_POST -def delete_thread(request, thread_id): +def delete_thread(request, course_id, thread_id): response = comment_client.delete_thread(thread_id) return JsonResponse(response) @thread_author_only @login_required @require_POST -def update_comment(request, comment_id): +def update_comment(request, course_id, comment_id): attributes = extract(request.POST, ['body']) response = comment_client.update_comment(comment_id, attributes) return JsonResponse(response) @@ -94,14 +94,14 @@ def update_comment(request, comment_id): @instructor_only @login_required @require_POST -def endorse_comment(request, comment_id): +def endorse_comment(request, course_id, comment_id): attributes = extract(request.POST, ['endorsed']) response = comment_client.update_comment(comment_id, attributes) return JsonResponse(response) @login_required @require_POST -def create_sub_comment(request, comment_id): +def create_sub_comment(request, course_id, comment_id): attributes = extract(request.POST, ['body']) attributes['user_id'] = request.user.id attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow @@ -111,69 +111,69 @@ def create_sub_comment(request, comment_id): @comment_author_only @login_required @require_POST -def delete_comment(request, comment_id): +def delete_comment(request, course_id, comment_id): response = comment_client.delete_comment(comment_id) return JsonResponse(response) @login_required @require_POST -def vote_for_comment(request, comment_id, value): +def vote_for_comment(request, course_id, comment_id, value): user_id = request.user.id response = comment_client.vote_for_comment(comment_id, user_id, value) return JsonResponse(response) @login_required @require_POST -def vote_for_thread(request, thread_id, value): +def vote_for_thread(request, course_id, thread_id, value): user_id = request.user.id response = comment_client.vote_for_thread(thread_id, user_id, value) return JsonResponse(response) @login_required @require_POST -def watch_thread(request, thread_id): +def watch_thread(request, course_id, thread_id): user_id = request.user.id response = comment_client.subscribe_thread(user_id, thread_id) return JsonResponse(response) @login_required @require_POST -def watch_commentable(request, commentable_id): +def watch_commentable(request, course_id, commentable_id): user_id = request.user.id response = comment_client.subscribe_commentable(user_id, commentable_id) return JsonResponse(response) @login_required @require_POST -def follow(request, followed_user_id): +def follow(request, course_id, followed_user_id): user_id = request.user.id response = comment_client.follow(user_id, followed_user_id) return JsonResponse(response) @login_required @require_POST -def unwatch_thread(request, thread_id): +def unwatch_thread(request, course_id, thread_id): user_id = request.user.id response = comment_client.unsubscribe_thread(user_id, thread_id) return JsonResponse(response) @login_required @require_POST -def unwatch_commentable(request, commentable_id): +def unwatch_commentable(request, course_id, commentable_id): user_id = request.user.id response = comment_client.unsubscribe_commentable(user_id, commentable_id) return JsonResponse(response) @login_required @require_POST -def unfollow(request, followed_user_id): +def unfollow(request, course_id, followed_user_id): user_id = request.user.id response = comment_client.unfollow(user_id, followed_user_id) return JsonResponse(response) @login_required @require_GET -def search(request): +def search(request, course_id): text = request.GET.get('text', None) commentable_id = request.GET.get('commentable_id', None) response = comment_client.search(text, commentable_id) diff --git a/lms/djangoapps/django_comment_client/forum/urls.py b/lms/djangoapps/django_comment_client/forum/urls.py index 3a724ebee4..0fd4f0e320 100644 --- a/lms/djangoapps/django_comment_client/forum/urls.py +++ b/lms/djangoapps/django_comment_client/forum/urls.py @@ -4,5 +4,5 @@ import django_comment_client.forum.views urlpatterns = patterns('django_comment_client.forum.views', url(r'search$', 'search', name='search'), url(r'threads/(?P\w+)$', 'single_thread', name='single_thread'), - url(r'(?P[\w\.\-]+)/(?P\w+)$', 'forum_form_discussion', name='forum_form_discussion'), + url(r'(?P\w+)$', 'forum_form_discussion', name='forum_form_discussion'), ) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index dfff24e5bf..c2a136d6cf 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -58,7 +58,7 @@ def get_categorized_discussion_info(request, user, course, course_name, url_cour return { 'title': module.title, 'discussion_id': module.discussion_id, - 'category': module.category, + 'category': module.discussion_category, } discussion_module_descriptors = map(_get_module_descriptor, @@ -90,27 +90,28 @@ def render_accordion(request, course, discussion_info, discussion_id): return render_to_string('discussion/accordion.html', context) -def render_discussion(request, threads, discussion_id=None, search_text=''): +def render_discussion(request, course_id, threads, discussion_id=None, search_text=''): context = { 'threads': threads, 'discussion_id': discussion_id, - 'search_bar': render_search_bar(request, discussion_id, text=search_text), + 'search_bar': render_search_bar(request, course_id, discussion_id, text=search_text), 'user_info': comment_client.get_user_info(request.user.id, raw=True), + 'course_id': course_id, } return render_to_string('discussion/inline.html', context) -def render_search_bar(request, discussion_id=None, text=''): +def render_search_bar(request, course_id, discussion_id=None, text=''): if not discussion_id: return '' context = { 'discussion_id': discussion_id, 'text': text, + 'course_id': course_id, } return render_to_string('discussion/search_bar.html', context) def forum_form_discussion(request, course_id, discussion_id): - course_id = course_id.replace('-', '/') course = check_course(course_id) _, course_name, _ = course_id.split('/') @@ -131,39 +132,46 @@ def forum_form_discussion(request, course_id, discussion_id): 'COURSE_TITLE': course.title, 'course': course, 'init': '', - 'content': render_discussion(request, threads, discussion_id, search_text), + 'content': render_discussion(request, course_id, threads, discussion_id, search_text), 'accordion': render_accordion(request, course, discussion_info, discussion_id), } return render_to_response('discussion/index.html', context) -def render_single_thread(request, thread_id): +def render_single_thread(request, course_id, thread_id): context = { 'thread': comment_client.get_thread(thread_id, recursive=True), 'user_info': comment_client.get_user_info(request.user.id, raw=True), + 'course_id': course_id, } return render_to_string('discussion/single_thread.html', context) -def single_thread(request, thread_id): +def single_thread(request, course_id, thread_id): + + course = check_course(course_id) context = { 'csrf': csrf(request)['csrf_token'], 'init': '', - 'content': render_single_thread(request, thread_id), + 'content': render_single_thread(request, course_id, thread_id), 'accordion': '', 'user_info': comment_client.get_user_info(request.user.id, raw=True), + 'course': course, } return render_to_response('discussion/index.html', context) -def search(request): +def search(request, course_id): + + course = check_course(course_id) text = request.GET.get('text', None) threads = comment_client.search(text) context = { 'csrf': csrf(request)['csrf_token'], 'init': '', - 'content': render_discussion(request, threads, search_text=text), + 'content': render_discussion(request, course_id, threads, search_text=text), 'accordion': '', + 'course': course, } return render_to_response('discussion/index.html', context) diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index fe0007a8f0..44aebb7241 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -26,23 +26,23 @@ Discussion = urlFor: (name, param) -> { - watch_commentable : "/discussions/#{param}/watch" - unwatch_commentable : "/discussions/#{param}/unwatch" - create_thread : "/discussions/#{param}/threads/create" - update_thread : "/discussions/threads/#{param}/update" - create_comment : "/discussions/threads/#{param}/reply" - delete_thread : "/discussions/threads/#{param}/delete" - upvote_thread : "/discussions/threads/#{param}/upvote" - downvote_thread : "/discussions/threads/#{param}/downvote" - watch_thread : "/discussions/threads/#{param}/watch" - unwatch_thread : "/discussions/threads/#{param}/unwatch" - update_comment : "/discussions/comments/#{param}/update" - endorse_comment : "/discussions/comments/#{param}/endorse" - create_sub_comment : "/discussions/comments/#{param}/reply" - delete_comment : "/discussions/comments/#{param}/delete" - upvote_comment : "/discussions/comments/#{param}/upvote" - downvote_comment : "/discussions/comments/#{param}/downvote" - search : "/discussions/forum/search" + watch_commentable : "/courses/#{$$course_id}/discussion/#{param}/watch" + unwatch_commentable : "/courses/#{$$course_id}/discussion/#{param}/unwatch" + create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create" + update_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/update" + create_comment : "/courses/#{$$course_id}/discussion/threads/#{param}/reply" + delete_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/delete" + upvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/upvote" + downvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/downvote" + watch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/watch" + unwatch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unwatch" + update_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/update" + endorse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/endorse" + create_sub_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/reply" + delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete" + upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote" + downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote" + search : "/courses/#{$$course_id}/discussion/forum/search" }[name] handleAnchorAndReload: (response) -> @@ -54,11 +54,36 @@ Discussion = $content = $(content) $local = generateLocal($content.children(".discussion-content")) id = $content.attr("_id") - if id in user_info.upvoted_ids + if id in $$user_info.upvoted_ids $local(".discussion-vote-up").addClass("voted") - else if id in user_info.downvoted_ids + else if id in $$user_info.downvoted_ids $local(".discussion-vote-down").addClass("voted") + initializeWatchDiscussion = (discussion) -> + $discussion = $(discussion) + id = $discussion.attr("_id") + $local = generateLocal($discussion.children(".discussion-non-content")) + + handleWatchDiscussion = (elem) -> + url = Discussion.urlFor('watch_commentable', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + handleUnwatchDiscussion = (elem) -> + url = Discussion.urlFor('unwatch_commentable', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + if id in $$user_info.subscribed_commentable_ids + unwatchDiscussion = generateDiscussionLink("discussion-unwatch-discussion", "Unwatch", handleUnwatchDiscussion) + $local(".discussion-title-wrapper").append(unwatchDiscussion) + else + watchDiscussion = generateDiscussionLink("discussion-watch-discussion", "Watch", handleWatchDiscussion) + $local(".discussion-title-wrapper").append(watchDiscussion) initializeWatchThreads = (index, thread) -> $thread = $(thread) @@ -67,7 +92,6 @@ Discussion = handleWatchThread = (elem) -> url = Discussion.urlFor('watch_thread', id) - console.log url $.post url, {}, (response, textStatus) -> if textStatus == "success" Discussion.handleAnchorAndReload(response) @@ -80,16 +104,17 @@ Discussion = Discussion.handleAnchorAndReload(response) , 'json' - if id in user_info.subscribed_thread_ids + if id in $$user_info.subscribed_thread_ids unwatchThread = generateDiscussionLink("discussion-unwatch-thread", "Unwatch", handleUnwatchThread) $local(".info").append(unwatchThread) else watchThread = generateDiscussionLink("discussion-watch-thread", "Watch", handleWatchThread) $local(".info").append(watchThread) - if user_info? + if $$user_info? $(discussion).find(".comment").each(initializeVote) $(discussion).find(".thread").each(initializeVote).each(initializeWatchThreads) + initializeWatchDiscussion(discussion) bindContentEvents: (content) -> diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 724d7b15cf..cf669a10c2 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -37,6 +37,7 @@ $discussion_input_width: 60%; @include discussion-font; } } + .discussion-title { @include discussion-font; @include discussion-clickable; @@ -45,6 +46,22 @@ $discussion_input_width: 60%; margin-bottom: 20px; display: block; } + .discussion-title-wrapper { + .discussion-watch-discussion, .discussion-unwatch-discussion { + display: none; + @include discussion-font; + margin-left: 5px; + font-size: $comment_info_size; + } + .discussion-title { + display: inline-block; + } + &:hover { + .discussion-watch-discussion, .discussion-unwatch-discussion { + display: inline-block; + } + } + } .discussion-votes { margin-right: 8px; margin-top: 5px; diff --git a/lms/templates/course_navigation.html b/lms/templates/course_navigation.html index beaea5e2a7..4682fc423f 100644 --- a/lms/templates/course_navigation.html +++ b/lms/templates/course_navigation.html @@ -15,7 +15,7 @@ def url_class(url):
  • Course Info
  • % if user.is_authenticated():
  • Textbook
  • -
  • Discussion
  • +
  • Discussion
  • % endif
  • Wiki
  • % if user.is_authenticated(): diff --git a/lms/templates/discussion/accordion.html b/lms/templates/discussion/accordion.html index 99c72a8f96..c072705d55 100644 --- a/lms/templates/discussion/accordion.html +++ b/lms/templates/discussion/accordion.html @@ -1,23 +1,23 @@ <%! from django.core.urlresolvers import reverse %> <% -def url_for(board): - return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id.replace('/', '-'), board['discussion_id']]) +def url_for(commentable): + return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id, commentable['discussion_id']]) %> -<%def name="make_category(category, boards)"> +<%def name="make_category(category, commentables)">

    ${category}

    -% for category, boards in discussion_info.items(): - ${make_category(category, boards)} +% for category, commentables in discussion_info.items(): + ${make_category(category, commentables)} % endfor diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index 7b35863377..2beac5ddd9 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -10,7 +10,7 @@ <%block name="js_extra"> -##<%include file="../course_navigation.html" args="active_page='discussion'" /> +<%include file="../course_navigation.html" args="active_page='discussion'" />
    diff --git a/lms/templates/discussion/inline.html b/lms/templates/discussion/inline.html index d9d5bb8df1..779a08e6f6 100644 --- a/lms/templates/discussion/inline.html +++ b/lms/templates/discussion/inline.html @@ -1,8 +1,10 @@ <%namespace name="renderer" file="thread.html"/> -
    +
    - Discussion + ${search_bar}
    @@ -11,11 +13,11 @@
    % for thread in threads: - ${renderer.render_thread(thread, edit_thread=False, show_comments=False)} + ${renderer.render_thread(course_id, thread, edit_thread=False, show_comments=False)} % endfor
    - diff --git a/lms/templates/discussion/search_bar.html b/lms/templates/discussion/search_bar.html index dfc6ab5e2f..bf3ec6a02f 100644 --- a/lms/templates/discussion/search_bar.html +++ b/lms/templates/discussion/search_bar.html @@ -2,7 +2,7 @@ <% def url_for_search(): - return reverse('django_comment_client.forum.views.search') + return reverse('django_comment_client.forum.views.search', args=[course_id]) %>
    diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html index 0545b2ba4a..3e320b68ca 100644 --- a/lms/templates/discussion/single_thread.html +++ b/lms/templates/discussion/single_thread.html @@ -2,10 +2,10 @@
    Discussion - ${renderer.render_thread(thread, edit_thread=True, show_comments=True)} + ${renderer.render_thread(course_id, thread, edit_thread=True, show_comments=True)}
    - diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html index f76d3de8df..fbfa0f19c8 100644 --- a/lms/templates/discussion/thread.html +++ b/lms/templates/discussion/thread.html @@ -2,13 +2,13 @@ <%! from datehelper import time_ago_in_words %> <%! from dateutil.parser import parse %> -<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> +<%def name="render_thread(course_id, thread, edit_thread=False, show_comments=False)"> <% if show_comments: url_for_thread = "" else: thread_id = thread['id'] - url_for_thread = reverse('django_comment_client.forum.views.single_thread', args=[thread_id]) + url_for_thread = reverse('django_comment_client.forum.views.single_thread', args=[course_id, thread_id]) %>
    diff --git a/lms/urls.py b/lms/urls.py index b96d9aecf4..69fa474e8f 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -130,6 +130,10 @@ if settings.COURSEWARE_ENABLED: 'courseware.views.profile', name="profile"), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/profile/(?P[^/]*)/$', 'courseware.views.profile'), + + # discussion + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/discussion/', + include('django_comment_client.urls')), ) # Multicourse wiki @@ -154,8 +158,6 @@ if settings.ASKBOT_ENABLED: # url(r'^robots.txt$', include('robots.urls')), ) -# discussion -urlpatterns += (url(r'^discussions/', include('django_comment_client.urls')), ) if settings.DEBUG: ## Jasmine From 54ea3dba227907b79ee1e99f82a92474c6798bf6 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Wed, 25 Jul 2012 15:30:05 -0400 Subject: [PATCH 016/352] show news & options when submit comment --- lms/djangoapps/courseware/views.py | 29 ++++++ .../django_comment_client/base/views.py | 11 ++- .../django_comment_client/forum/views.py | 70 +------------- lms/djangoapps/django_comment_client/utils.py | 93 +++++++++++++++++++ lms/static/coffee/src/discussion.coffee | 37 ++++++-- lms/static/sass/_discussion.scss | 3 +- lms/static/sass/_news.scss | 19 ++++ lms/static/sass/application.scss | 1 + lms/templates/course_navigation.html | 1 + lms/templates/discussion/thread.html | 31 ++++--- lms/templates/news.html | 21 +++++ lms/templates/notifications.html | 48 ++++++++++ lms/templates/profile.html | 4 +- lms/urls.py | 2 + 14 files changed, 278 insertions(+), 92 deletions(-) create mode 100644 lms/djangoapps/django_comment_client/utils.py create mode 100644 lms/static/sass/_news.scss create mode 100644 lms/templates/news.html create mode 100644 lms/templates/notifications.html diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index c8391e0483..4d424d595f 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -1,6 +1,8 @@ import logging import urllib +from functools import partial + from django.conf import settings from django.core.context_processors import csrf from django.contrib.auth.models import User @@ -16,6 +18,7 @@ from module_render import toc_for_course, get_module, get_section from models import StudentModuleCache from student.models import UserProfile from multicourse import multicourse_settings +from django_comment_client.utils import get_discussion_title from util.cache import cache, cache_if_anonymous from student.models import UserTestGroup @@ -23,6 +26,11 @@ from courseware import grades from courseware.courses import check_course from xmodule.modulestore.django import modulestore +import comment_client + + + + log = logging.getLogger("mitx.courseware") template_imports = {'urllib': urllib} @@ -249,3 +257,24 @@ def course_info(request, course_id): course = check_course(course_id) return render_to_response('info.html', {'course': course}) + +def render_notifications(request, course, notifications): + context = { + 'notifications': notifications, + 'get_discussion_title': partial(get_discussion_title, request=request, course=course), + 'course': course, + } + return render_to_string('notifications.html', context) + +@login_required +def news(request, course_id): + course = check_course(course_id) + + notifications = comment_client.get_notifications(request.user.id) + + context = { + 'course': course, + 'content': render_notifications(request, course, notifications), + } + + return render_to_response('news.html', context) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index a148d80020..85e76bad55 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -71,8 +71,11 @@ def update_thread(request, course_id, thread_id): @require_POST def create_comment(request, course_id, thread_id): attributes = extract(request.POST, ['body']) - attributes['user_id'] = request.user.id + if request.POST.get('anonymous', 'false').lower() == 'false': + attributes['user_id'] = request.user.id attributes['course_id'] = course_id + attributes['auto_subscribe'] = bool(request.POST.get('autowatch', False)) + print attributes response = comment_client.create_comment(thread_id, attributes) return JsonResponse(response) @@ -103,8 +106,10 @@ def endorse_comment(request, course_id, comment_id): @require_POST def create_sub_comment(request, course_id, comment_id): attributes = extract(request.POST, ['body']) - attributes['user_id'] = request.user.id - attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + if request.POST.get('anonymous', 'false').lower() == 'false': + attributes['user_id'] = request.user.id + attributes['course_id'] = course_id + attributes['auto_subscribe'] = bool(request.POST.get('autowatch', False)) response = comment_client.create_sub_comment(comment_id, attributes) return JsonResponse(response) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index c2a136d6cf..bde1ed7f51 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -6,80 +6,16 @@ from django.core.context_processors import csrf from mitxmako.shortcuts import render_to_response, render_to_string from courseware.courses import check_course -from courseware.models import StudentModuleCache -from courseware.module_render import get_module, get_section -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore - -from importlib import import_module - -from django.conf import settings import comment_client import dateutil from dateutil.tz import tzlocal from datehelper import time_ago_in_words -import operator -import itertools +from django_comment_client.utils import get_categorized_discussion_info + import json -_FULLMODULES = None -_DISCUSSIONINFO = None - -def get_full_modules(): - global _FULLMODULES - if not _FULLMODULES: - class_path = settings.MODULESTORE['default']['ENGINE'] - module_path, _, class_name = class_path.rpartition('.') - class_ = getattr(import_module(module_path), class_name) - modulestore = class_(eager=True, **settings.MODULESTORE['default']['OPTIONS']) - _FULLMODULES = modulestore.modules - return _FULLMODULES - -def get_categorized_discussion_info(request, user, course, course_name, url_course_id): - """ - return a dict of the form {category: modules} - """ - global _DISCUSSIONINFO - if not _DISCUSSIONINFO: - - _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ - and x[0].dict()['course'] == course_name - - _get_module_descriptor = operator.itemgetter(1) - - def _get_module(module_descriptor): - print module_descriptor - module = get_module(user, request, module_descriptor.location, student_module_cache)[0] - return module - - def _extract_info(module): - return { - 'title': module.title, - 'discussion_id': module.discussion_id, - 'category': module.discussion_category, - } - - discussion_module_descriptors = map(_get_module_descriptor, - filter(_is_course_discussion, - get_full_modules().items())) - - student_module_cache = StudentModuleCache(user, course) - - discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) - - _DISCUSSIONINFO = dict((category, list(l)) \ - for category, l in itertools.groupby(discussion_info, operator.itemgetter('category'))) - - _DISCUSSIONINFO['General'] = [{ - 'title': 'General discussion', - 'discussion_id': url_course_id, - 'category': 'General', - }] - - return _DISCUSSIONINFO - def render_accordion(request, course, discussion_info, discussion_id): context = { 'course': course, @@ -118,7 +54,7 @@ def forum_form_discussion(request, course_id, discussion_id): url_course_id = course_id.replace('/', '_').replace('.', '_') - discussion_info = get_categorized_discussion_info(request, request.user, course, course_name, url_course_id) + discussion_info = get_categorized_discussion_info(request, course)#request.user, course, course_name, url_course_id) search_text = request.GET.get('text', '') diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py new file mode 100644 index 0000000000..24f4f5115a --- /dev/null +++ b/lms/djangoapps/django_comment_client/utils.py @@ -0,0 +1,93 @@ +from importlib import import_module +from courseware.models import StudentModuleCache +from courseware.module_render import get_module +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore + +from django.conf import settings +import operator +import itertools + +_FULLMODULES = None +_DISCUSSIONINFO = None + +def get_full_modules(): + global _FULLMODULES + if not _FULLMODULES: + class_path = settings.MODULESTORE['default']['ENGINE'] + module_path, _, class_name = class_path.rpartition('.') + class_ = getattr(import_module(module_path), class_name) + modulestore = class_(eager=True, **settings.MODULESTORE['default']['OPTIONS']) + _FULLMODULES = modulestore.modules + return _FULLMODULES + +def get_categorized_discussion_info(request, course): + """ + return a dict of the form {category: modules} + """ + global _DISCUSSIONINFO + if not _DISCUSSIONINFO: + initialize_discussion_info(request, course) + return _DISCUSSIONINFO['categorized'] + +def get_discussion_title(request, course, discussion_id): + global _DISCUSSIONINFO + if not _DISCUSSIONINFO: + initialize_discussion_info(request, course) + title = _DISCUSSIONINFO['by_id'].get(discussion_id, {}).get('title', '(no title)') + if title == '(no title)': + print "title shouldn't be none" + import pdb; pdb.set_trace() + return title + +def initialize_discussion_info(request, course): + + global _DISCUSSIONINFO + if _DISCUSSIONINFO: + return + + course_id = course.id + _, course_name, _ = course_id.split('/') + user = request.user + url_course_id = course_id.replace('/', '_').replace('.', '_') + + _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ + and x[0].dict()['course'] == course_name + + _get_module_descriptor = operator.itemgetter(1) + + def _get_module(module_descriptor): + print module_descriptor + module = get_module(user, request, module_descriptor.location, student_module_cache)[0] + return module + + def _extract_info(module): + return { + 'title': module.title, + 'discussion_id': module.discussion_id, + 'category': module.discussion_category, + } + + def _pack_with_id(info): + return (info['discussion_id'], info) + + discussion_module_descriptors = map(_get_module_descriptor, + filter(_is_course_discussion, + get_full_modules().items())) + + student_module_cache = StudentModuleCache(user, course) + + discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) + + _DISCUSSIONINFO = {} + + _DISCUSSIONINFO['by_id'] = dict(map(_pack_with_id, discussion_info)) + + _DISCUSSIONINFO['categorized'] = dict((category, list(l)) \ + for category, l in itertools.groupby(discussion_info, operator.itemgetter('category'))) + + _DISCUSSIONINFO['categorized']['General'] = [{ + 'title': 'General discussion', + 'discussion_id': url_course_id, + 'category': 'General', + }] diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index 44aebb7241..51420f38cc 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -122,6 +122,8 @@ Discussion = $discussionContent = $content.children(".discussion-content") $local = generateLocal($discussionContent) + id = $content.attr("_id") + discussionContentHoverIn = -> status = $discussionContent.attr("status") || "normal" if status == "normal" @@ -138,15 +140,32 @@ Discussion = $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) - - handleReply = (elem) -> editView = $local(".discussion-content-edit") if editView.length editView.show() else editView = $("
    ").addClass("discussion-content-edit") - editView.append($(" + New Post +
    % for thread in threads:
    From 3267aa1737ad230c88b0da3073ac411256f86267 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Sun, 22 Jul 2012 19:05:32 -0400 Subject: [PATCH 022/352] forum view for discussions --- .../lib/xmodule/xmodule/discussion_module.py | 3 +- lms/static/coffee/src/discussion.coffee | 134 ------------------ lms/static/sass/_discussion.scss | 114 --------------- lms/templates/course_navigation.html | 26 ---- lms/templates/discussion.html | 58 -------- 5 files changed, 2 insertions(+), 333 deletions(-) delete mode 100644 lms/static/coffee/src/discussion.coffee delete mode 100644 lms/static/sass/_discussion.scss delete mode 100644 lms/templates/course_navigation.html delete mode 100644 lms/templates/discussion.html diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 1def043503..3e9c133002 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -16,13 +16,14 @@ class DiscussionModule(XModule): 'parse': dateutil.parser.parse, 'commentable_id': self.discussion_id, } - return self.system.render_template('discussion.html', context) + return self.system.render_template('discussion/inline.html', context) def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) xml_data = etree.fromstring(definition['data']) self.discussion_id = xml_data.attrib['id'] self.title = xml_data.attrib['for'] + self.category = xml_data.attrib['category'] class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee deleted file mode 100644 index fb8bfbef24..0000000000 --- a/lms/static/coffee/src/discussion.coffee +++ /dev/null @@ -1,134 +0,0 @@ -$ -> - - #DEBUG = true - DEBUG = false - - $(".discussion-title").click -> - $thread = $(this).parent().children(".thread") - if $thread.css("display") == "none" - $thread.show() - else - $thread.hide() - - $(".thread-title").click -> - $comments = $(this).parent().parent().children(".comments") - if $comments.css("display") == "none" - $comments.show() - else - $comments.hide() - - getDiscussionContentLink = ($elem, selector) -> - $elem.children(".discussion-content-view").children(".info").children(selector) - - discussionContentHoverIn = -> - status = $(this).attr("status") || "normal" - if status == "normal" - getDiscussionContentLink($(this), ".discussion-link").show() - else if status == "reply" - getDiscussionContentLink($(this), ".discussion-cancel-reply").show() - getDiscussionContentLink($(this), ".discussion-submit-reply").show() - else if status == "edit" - getDiscussionContentLink($(this), ".discussion-cancel-edit").show() - getDiscussionContentLink($(this), ".discussion-update-edit").show() - - discussionContentHoverOut = -> - getDiscussionContentLink($(this), ".discussion-link").hide() - - $(".discussion-content").hover(discussionContentHoverIn, discussionContentHoverOut) - - $(".discussion-reply").click -> - handleReply(this) - - $(".discussion-cancel-reply").click -> - handleCancelReply(this) - - $(".discussion-new-post").click -> - handleSubmitNewThread(this) - - discussionLink = (cls, txt, handler) -> - $("").addClass("discussion-link"). - attr("href", "javascript:void(0)"). - addClass(cls).html(txt). - click(-> handler(this)) - - handleReply = (elem) -> - discussionContent = $(elem).parents(".discussion-content") - editView = discussionContent.children(".discussion-content-edit") - if editView.length - editView.show() - else - editView = $("
    ").addClass("discussion-content-edit") - editView.append($(" - New Post -
    - % for thread in threads: -
    -
    - ${thread['title']} -
    -
    ${thread['body']}
    -
    - ${render_info(thread)} - ${render_reply('')} - ${render_edit('')} -
    -
    -
    -
    - ${render_comments(thread['children'])} -
    -
    - % endfor -
    - -<%def name="render_comments(comments)"> - % for comment in comments: -
    -
    -
    - ${comment['body']} -
    - ${render_info(comment)} - ${render_reply('')} - ${render_edit('')} -
    -
    -
    -
    - ${render_comments(comment['children'])} -
    -
    - % endfor - - -<%def name="render_info(content)"> - ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} - - -<%def name="render_reply(url)"> - Reply - - -<%def name="render_edit(url)"> - Edit - From d49338c94815570e21435e2c5916220ed26b9e72 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 15:41:09 -0400 Subject: [PATCH 023/352] integrated search; refactored some frontend code --- .../lib/xmodule/xmodule/discussion_module.py | 5 +- lms/static/coffee/src/discussion.coffee | 146 ++++++++++++++++++ lms/static/sass/_discussion.scss | 134 ++++++++++++++++ lms/templates/course_navigation.html | 26 ++++ lms/templates/discussion/accordion.html | 23 +++ lms/templates/discussion/index.html | 39 +++++ lms/templates/discussion/inline.html | 78 ++++++++++ lms/templates/discussion/search_bar.html | 13 ++ lms/templates/discussion/single_thread.html | 61 ++++++++ 9 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 lms/static/coffee/src/discussion.coffee create mode 100644 lms/static/sass/_discussion.scss create mode 100644 lms/templates/course_navigation.html create mode 100644 lms/templates/discussion/accordion.html create mode 100644 lms/templates/discussion/index.html create mode 100644 lms/templates/discussion/inline.html create mode 100644 lms/templates/discussion/search_bar.html create mode 100644 lms/templates/discussion/single_thread.html diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 3e9c133002..e1c41beeb6 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -11,10 +11,11 @@ from datehelper import time_ago_in_words class DiscussionModule(XModule): def get_html(self): context = { - 'threads': comment_client.get_threads(self.discussion_id, recursive=True), + 'threads': comment_client.get_threads(self.discussion_id, recursive=False), 'time_ago_in_words': time_ago_in_words, 'parse': dateutil.parser.parse, - 'commentable_id': self.discussion_id, + 'discussion_id': self.discussion_id, + 'search_bar': '', } return self.system.render_template('discussion/inline.html', context) diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee new file mode 100644 index 0000000000..7aea3507c7 --- /dev/null +++ b/lms/static/coffee/src/discussion.coffee @@ -0,0 +1,146 @@ +$ -> + + if $('#accordion').length + active = $('#accordion ul:has(li.active)').index('#accordion ul') + $('#accordion').bind('accordionchange', @log).accordion + active: if active >= 0 then active else 1 + header: 'h3' + autoHeight: false + $('#open_close_accordion a').click @toggle + $('#accordion').show() + + $("section.discussion").each (index, discussion) -> + Discussion.bindDiscussionEvents(discussion) + +Discussion = + + urlFor: (name, param) -> + { + create_thread : "/discussions/#{param}/threads/create" + update_thread : "/discussions/threads/#{param}/update" + create_comment : "/discussions/threads/#{param}/reply" + delete_thread : "/discussions/threads/#{param}/delete" + update_comment : "/discussions/comments/#{param}/update" + endorse_comment : "/discussions/comments/#{param}/endorse" + create_sub_comment : "/discussions/comments/#{param}/reply" + delete_comment : "/discussions/comments/#{param}/delete" + upvote_comment : "/discussions/comments/#{param}/upvote" + downvote_comment : "/discussions/comments/#{param}/downvote" + upvote_thread : "/discussions/threads/#{param}/upvote" + downvote_thread : "/discussions/threads/#{param}/downvote" + search : "/discussions/forum/search" + }[name] + + handleAnchorAndReload: (response) -> + window.location = window.location.pathname + "#" + response['id'] + window.location.reload() + + bindContentEvents: (content) -> + + $content = $(content) + $discussionContent = $content.children(".discussion-content") + $local = (selector) -> $discussionContent.find(selector) + + discussionContentHoverIn = -> + status = $discussionContent.attr("status") || "normal" + if status == "normal" + $local(".discussion-link").show() + else if status == "reply" + $local(".discussion-cancel-reply").show() + $local(".discussion-submit-reply").show() + else if status == "edit" + $local(".discussion-cancel-edit").show() + $local(".discussion-update-edit").show() + + discussionContentHoverOut = -> + $local(".discussion-link").hide() + + $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) + + generateDiscussionLink = (cls, txt, handler) -> + $("").addClass("discussion-link"). + attr("href", "javascript:void(0)"). + addClass(cls).html(txt). + click(-> handler(this)) + + handleReply = (elem) -> + editView = $local(".discussion-content-edit") + if editView.length + editView.show() + else + editView = $("
    ").addClass("discussion-content-edit") + editView.append($(" + New Post + +
    + % for thread in threads: + ${render_thread(thread)} + % endfor +
    + +<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> +
    +
    + ${thread['title']} +
    +
    ${thread['body']}
    +
    + ${render_info(thread)} + % if edit_thread: + ${render_reply('')} + ${render_edit('')} + % endif +
    +
    +
    + % if show_comments: +
    + ${render_comments(thread['children'])} +
    + % endif +
    + + +<%def name="render_comments(comments)"> + % for comment in comments: +
    +
    +
    + ${comment['body']} +
    + ${render_info(comment)} + ${render_reply('')} + ${render_edit('')} +
    +
    +
    +
    + ${render_comments(comment['children'])} +
    +
    + % endfor + + +<%def name="render_info(content)"> + ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} + + +<%def name="render_reply(url)"> + Reply + + +<%def name="render_edit(url)"> + Edit + diff --git a/lms/templates/discussion/search_bar.html b/lms/templates/discussion/search_bar.html new file mode 100644 index 0000000000..dfc6ab5e2f --- /dev/null +++ b/lms/templates/discussion/search_bar.html @@ -0,0 +1,13 @@ +<%! from django.core.urlresolvers import reverse %> + +<% +def url_for_search(): + return reverse('django_comment_client.forum.views.search') +%> + +
    + + + + Search +
    diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html new file mode 100644 index 0000000000..4a009c10cb --- /dev/null +++ b/lms/templates/discussion/single_thread.html @@ -0,0 +1,61 @@ +<%! from django.core.urlresolvers import reverse %> + +
    + Discussion + ${render_thread(thread, edit_thread=True, show_comments=True)} +
    + +<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> +
    +
    + ${thread['title']} +
    +
    ${thread['body']}
    +
    + ${render_info(thread)} + % if edit_thread: + ${render_reply('')} + ${render_edit('')} + % endif +
    +
    +
    + % if show_comments: +
    + ${render_comments(thread['children'])} +
    + % endif +
    + + +<%def name="render_comments(comments)"> + % for comment in comments: +
    +
    +
    + ${comment['body']} +
    + ${render_info(comment)} + ${render_reply('')} + ${render_edit('')} +
    +
    +
    +
    + ${render_comments(comment['children'])} +
    +
    + % endfor + + +<%def name="render_info(content)"> + ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} + + +<%def name="render_reply(url)"> + Reply + + +<%def name="render_edit(url)"> + Edit + From 28113d8b434985414ff1e97ea421c0c0f3ca6e9b Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 16:20:45 -0400 Subject: [PATCH 024/352] moved django_comment_client into edx --- .../django_comment_client/__init__.py | 0 .../django_comment_client/base/__init__.py | 0 .../django_comment_client/base/urls.py | 17 ++ .../django_comment_client/base/views.py | 141 +++++++++++++++ .../django_comment_client/forum/__init__.py | 0 .../django_comment_client/forum/urls.py | 8 + .../django_comment_client/forum/views.py | 168 ++++++++++++++++++ lms/djangoapps/django_comment_client/urls.py | 6 + 8 files changed, 340 insertions(+) create mode 100644 lms/djangoapps/django_comment_client/__init__.py create mode 100644 lms/djangoapps/django_comment_client/base/__init__.py create mode 100644 lms/djangoapps/django_comment_client/base/urls.py create mode 100644 lms/djangoapps/django_comment_client/base/views.py create mode 100644 lms/djangoapps/django_comment_client/forum/__init__.py create mode 100644 lms/djangoapps/django_comment_client/forum/urls.py create mode 100644 lms/djangoapps/django_comment_client/forum/views.py create mode 100644 lms/djangoapps/django_comment_client/urls.py diff --git a/lms/djangoapps/django_comment_client/__init__.py b/lms/djangoapps/django_comment_client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/base/__init__.py b/lms/djangoapps/django_comment_client/base/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/base/urls.py b/lms/djangoapps/django_comment_client/base/urls.py new file mode 100644 index 0000000000..ec3ba7c625 --- /dev/null +++ b/lms/djangoapps/django_comment_client/base/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls.defaults import url, patterns +import django_comment_client.base.views + +urlpatterns = patterns('django_comment_client.base.views', + url(r'(?P[\w\-]+)/threads/create$', 'create_thread', name='create_thread'), + url(r'threads/(?P[\w\-]+)/update$', 'update_thread', name='update_thread'), + url(r'threads/(?P[\w\-]+)/reply$', 'create_comment', name='create_comment'), + url(r'threads/(?P[\w\-]+)/delete', 'delete_thread', name='delete_thread'), + url(r'comments/(?P[\w\-]+)/update$', 'update_comment', name='update_comment'), + url(r'comments/(?P[\w\-]+)/endorse$', 'endorse_comment', name='endorse_comment'), + url(r'comments/(?P[\w\-]+)/reply$', 'create_sub_comment', name='create_sub_comment'), + url(r'comments/(?P[\w\-]+)/delete$', 'delete_comment', name='delete_comment'), + url(r'comments/(?P[\w\-]+)/upvote$', 'vote_for_comment', {'value': 'up'}, name='upvote_comment'), + url(r'comments/(?P[\w\-]+)/downvote$', 'vote_for_comment', {'value': 'down'}, name='downvote_comment'), + url(r'threads/(?P[\w\-]+)/upvote$', 'vote_for_thread', {'value': 'up'}, name='upvote_thread'), + url(r'threads/(?P[\w\-]+)/downvote$', 'vote_for_thread', {'value': 'down'}, name='downvote_thread'), +) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py new file mode 100644 index 0000000000..291bb4c732 --- /dev/null +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -0,0 +1,141 @@ +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_POST, require_GET +from django.http import HttpResponse +from django.utils import simplejson + +import comment_client + +class JsonResponse(HttpResponse): + def __init__(self, data=None): + content = simplejson.dumps(data, + indent=2, + ensure_ascii=False) + super(JsonResponse, self).__init__(content, + mimetype='application/json; charset=utf8') + +class JsonError(HttpResponse): + def __init__(self, status, error_message=""): + content = simplejson.dumps({'errors': error_message}, + indent=2, + ensure_ascii=False) + super(JsonError, self).__init__(content, + status=status, + mimetype='application/json; charset=utf8') + +def thread_author_only(fn): + def verified_fn(request, *args, **kwargs): + thread_id = args.get('thread_id', False) or \ + kwargs.get('thread_id', False) + thread = comment_client.get_thread(thread_id) + if request.user.id == thread['user_id']: + return fn(request, *args, **kwargs) + else: + return JsonError(400, "unauthorized") + return verified_fn + +def comment_author_only(fn): + def verified_fn(request, *args, **kwargs): + comment_id = args.get('comment_id', False) or \ + kwargs.get('comment_id', False) + comment = comment_client.get_comment(comment_id) + if request.user.id == comment['user_id']: + return fn(request, *args, **kwargs) + else: + return JsonError(400, "unauthorized") + return verified_fn + +def instructor_only(fn): #TODO add instructor verification + return fn + +def extract(dic, keys): + return {k: dic[k] for k in keys} + +@login_required +@require_POST +def create_thread(request, commentable_id): + attributes = extract(request.POST, ['body', 'title']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_thread(commentable_id, attributes) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def update_thread(request, thread_id): + attributes = extract(request.POST, ['body', 'title']) + response = comment_client.update_thread(thread_id, attributes) + return JsonResponse(response) + +@login_required +@require_POST +def create_comment(request, thread_id): + attributes = extract(request.POST, ['body']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_comment(thread_id, attributes) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def delete_thread(request, thread_id): + response = comment_client.delete_thread(thread_id) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def update_comment(request, comment_id): + attributes = extract(request.POST, ['body']) + response = comment_client.update_comment(comment_id, attributes) + return JsonResponse(response) + +@instructor_only +@login_required +@require_POST +def endorse_comment(request, comment_id): + attributes = extract(request.POST, ['endorsed']) + response = comment_client.update_comment(comment_id, attributes) + return JsonResponse(response) + +@login_required +@require_POST +def create_sub_comment(request, comment_id): + attributes = extract(request.POST, ['body']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_sub_comment(comment_id, attributes) + return JsonResponse(response) + +@comment_author_only +@login_required +@require_POST +def delete_comment(request, comment_id): + response = comment_client.delete_comment(comment_id) + return JsonResponse(response) + +@login_required +@require_POST +def vote_for_comment(request, comment_id, value): + user_id = request.user.id + response = comment_client.vote_for_comment(comment_id, user_id, value) + return JsonResponse(response) + +@login_required +@require_POST +def vote_for_thread(request, thread_id, value): + user_id = request.user.id + response = comment_client.vote_for_thread(thread_id, user_id, value) + return JsonResponse(response) + +#undo vote: disabled for now + + +@login_required +@require_GET +def search(request): + text = request.GET.get('text', None) + commentable_id = request.GET.get('commentable_id', None) + response = comment_client.search(text, commentable_id) + return JsonResponse(response) diff --git a/lms/djangoapps/django_comment_client/forum/__init__.py b/lms/djangoapps/django_comment_client/forum/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/forum/urls.py b/lms/djangoapps/django_comment_client/forum/urls.py new file mode 100644 index 0000000000..3a724ebee4 --- /dev/null +++ b/lms/djangoapps/django_comment_client/forum/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import url, patterns +import django_comment_client.forum.views + +urlpatterns = patterns('django_comment_client.forum.views', + url(r'search$', 'search', name='search'), + url(r'threads/(?P\w+)$', 'single_thread', name='single_thread'), + url(r'(?P[\w\.\-]+)/(?P\w+)$', 'forum_form_discussion', name='forum_form_discussion'), +) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py new file mode 100644 index 0000000000..f6f893705e --- /dev/null +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -0,0 +1,168 @@ +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_POST +from django.http import HttpResponse +from django.utils import simplejson +from django.core.context_processors import csrf + +from mitxmako.shortcuts import render_to_response, render_to_string +from courseware.courses import check_course +from courseware.models import StudentModuleCache +from courseware.module_render import get_module, get_section +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore + +from importlib import import_module + +from django.conf import settings + +import comment_client +import dateutil +from dateutil.tz import tzlocal +from datehelper import time_ago_in_words + +import operator +import itertools + +_FULLMODULES = None +_DISCUSSIONINFO = None + +def get_full_modules(): + global _FULLMODULES + if not _FULLMODULES: + class_path = settings.MODULESTORE['default']['ENGINE'] + module_path, _, class_name = class_path.rpartition('.') + class_ = getattr(import_module(module_path), class_name) + modulestore = class_(eager=True, **settings.MODULESTORE['default']['OPTIONS']) + _FULLMODULES = modulestore.modules + return _FULLMODULES + +def get_categorized_discussion_info(request, user, course, course_name, url_course_id): + """ + return a dict of the form {category: modules} + """ + global _DISCUSSIONINFO + if not _DISCUSSIONINFO: + + _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ + and x[0].dict()['course'] == course_name + + _get_module_descriptor = operator.itemgetter(1) + + def _get_module(module_descriptor): + print module_descriptor + module = get_module(user, request, module_descriptor.location, student_module_cache)[0] + return module + + def _extract_info(module): + return { + 'title': module.title, + 'discussion_id': module.discussion_id, + 'category': module.category, + } + + discussion_module_descriptors = map(_get_module_descriptor, + filter(_is_course_discussion, + get_full_modules().items())) + + student_module_cache = StudentModuleCache(user, course) + + discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) + + _DISCUSSIONINFO = dict((category, list(l)) \ + for category, l in itertools.groupby(discussion_info, operator.itemgetter('category'))) + + _DISCUSSIONINFO['General'] = [{ + 'title': 'General discussion', + 'discussion_id': url_course_id, + 'category': 'General', + }] + + return _DISCUSSIONINFO + +def render_accordion(request, course, discussion_info, discussion_id): + context = dict([ + ('course', course), + ('discussion_info', discussion_info), + ('active', discussion_id), # TODO change this later + ('csrf', csrf(request)['csrf_token'])]) + + return render_to_string('discussion/accordion.html', context) + +def render_discussion(request, threads, discussion_id=None, search_text=''): + context = { + 'threads': threads, + 'time_ago_in_words': time_ago_in_words, + 'parse': dateutil.parser.parse, + 'discussion_id': discussion_id, + 'search_bar': render_search_bar(request, discussion_id, text=search_text), + } + return render_to_string('discussion/inline.html', context) + +def render_search_bar(request, discussion_id=None, text=''): + if not discussion_id: + return '' + context = { + 'discussion_id': discussion_id, + 'text': text, + } + return render_to_string('discussion/search_bar.html', context) + +def forum_form_discussion(request, course_id, discussion_id): + + course_id = course_id.replace('-', '/') + course = check_course(course_id) + + _, course_name, _ = course_id.split('/') + + url_course_id = course_id.replace('/', '_').replace('.', '_') + + discussion_info = get_categorized_discussion_info(request, request.user, course, course_name, url_course_id) + + search_text = request.GET.get('text', '') + + if len(search_text) > 0: + threads = comment_client.search(search_text, discussion_id) + else: + threads = comment_client.get_threads(discussion_id, recursive=False) + + context = { + 'csrf': csrf(request)['csrf_token'], + 'COURSE_TITLE': course.title, + 'course': course, + 'init': '', + 'content': render_discussion(request, threads, discussion_id, search_text), + 'accordion': render_accordion(request, course, discussion_info, discussion_id), + } + + return render_to_response('discussion/index.html', context) + +def render_single_thread(request, thread_id): + context = { + 'thread': comment_client.get_thread(thread_id, recursive=True), + 'time_ago_in_words': time_ago_in_words, + 'parse': dateutil.parser.parse, + } + return render_to_string('discussion/single_thread.html', context) + +def single_thread(request, thread_id): + + context = { + 'csrf': csrf(request)['csrf_token'], + 'init': '', + 'content': render_single_thread(request, thread_id), + 'accordion': '', + } + + return render_to_response('discussion/index.html', context) + +def search(request): + text = request.GET.get('text', None) + threads = comment_client.search(text) + context = { + 'csrf': csrf(request)['csrf_token'], + 'init': '', + 'content': render_discussion(request, threads, search_text=text), + 'accordion': '', + } + + return render_to_response('discussion/index.html', context) diff --git a/lms/djangoapps/django_comment_client/urls.py b/lms/djangoapps/django_comment_client/urls.py new file mode 100644 index 0000000000..959b5fa2ca --- /dev/null +++ b/lms/djangoapps/django_comment_client/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import url, patterns, include + +urlpatterns = patterns('', + url(r'forum/', include('django_comment_client.forum.urls')), + url(r'', include('django_comment_client.base.urls')), +) From 5e5dda2dfb238bae2501473de81d1edfea9b43df Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 19:03:36 -0400 Subject: [PATCH 025/352] refactored out thread rendering; vote --- lms/static/coffee/src/discussion.coffee | 17 +++- lms/static/sass/_discussion.scss | 39 ++++++++- lms/templates/discussion/inline.html | 66 +-------------- lms/templates/discussion/single_thread.html | 59 +------------- lms/templates/discussion/thread.html | 90 +++++++++++++++++++++ 5 files changed, 146 insertions(+), 125 deletions(-) create mode 100644 lms/templates/discussion/thread.html diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index 7aea3507c7..b3ee41203c 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -9,6 +9,7 @@ $ -> $('#open_close_accordion a').click @toggle $('#accordion').show() + $("section.discussion").each (index, discussion) -> Discussion.bindDiscussionEvents(discussion) @@ -32,7 +33,7 @@ Discussion = }[name] handleAnchorAndReload: (response) -> - window.location = window.location.pathname + "#" + response['id'] + #window.location = window.location.pathname + "#" + response['id'] window.location.reload() bindContentEvents: (content) -> @@ -100,12 +101,26 @@ Discussion = Discussion.handleAnchorAndReload(response) , 'json' + handleVote = (elem, value) -> + contentType = if $content.hasClass("thread") then "thread" else "comment" + url = Discussion.urlFor("#{value}vote_#{contentType}", $content.attr("_id")) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + $local(".discussion-reply").click -> handleReply(this) $local(".discussion-cancel-reply").click -> handleCancelReply(this) + $local(".discussion-vote-up").click -> + handleVote(this, "up") + + $local(".discussion-vote-down").click -> + handleVote(this, "down") + bindDiscussionEvents: (discussion) -> $discussion = $(discussion) $discussionNonContent = $discussion.children(".discussion-non-content") diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index b7820ff4a9..a5c2b5ed4f 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -1,4 +1,4 @@ -$comment_margin_left: 20px; +$comment_margin_left: 30px; $discussion_title_size: 1.6em; $comment_title_size: 1.2em; $comment_body_size: 1.0em; @@ -45,6 +45,33 @@ $discussion_input_width: 60%; margin-bottom: 20px; display: block; } + .discussion-votes { + margin-right: 8px; + margin-top: 5px; + text-align: center; + height: 40px; + float: left; + .discussion-vote-count { + font-size: $comment_body_size; + @include discussion-font; + } + a.discussion-vote { + display: block; + color: black; + font-weight: bold; + font-size: 15px; + &.discussion-vote-up { + margin-bottom: 3px; + } + &.discussion-vote-down { + margin-top: 3px; + } + } + } + .discussion-right-wrapper { + min-height: 40px; + float: left; + } .new-post-form { .new-post-title, .new-post-body { @include discussion-font; @@ -105,6 +132,8 @@ $discussion_input_width: 60%; } } .discussion-content { + margin-top: 10px; + overflow: hidden; .discussion-content-edit { .comment-edit { @include discussion-font; @@ -118,16 +147,20 @@ $discussion_input_width: 60%; .comments { //display: none; margin-left: $comment_margin_left; - + overflow: hidden; .comment { .comment-body { @include discussion-font; font-size: $comment_body_size; - margin-top: 10px; + margin-top: 3px; display: block; color: black; } } + .discussion-votes { + margin-right: 6px; + margin-top: 6px; + } } } diff --git a/lms/templates/discussion/inline.html b/lms/templates/discussion/inline.html index 91c905bf1a..e4f0ca43d3 100644 --- a/lms/templates/discussion/inline.html +++ b/lms/templates/discussion/inline.html @@ -1,11 +1,4 @@ -<%! from django.core.urlresolvers import reverse %> - -<% -def url_for(thread_id): - return reverse('django_comment_client.forum.views.single_thread', args=[thread_id]) -%> - - +<%namespace name="renderer" file="thread.html"/>
    @@ -18,61 +11,6 @@ def url_for(thread_id):
    % for thread in threads: - ${render_thread(thread)} + ${renderer.render_thread(thread, edit_thread=False, show_comments=False)} % endfor
    - -<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> -
    -
    - ${thread['title']} -
    -
    ${thread['body']}
    -
    - ${render_info(thread)} - % if edit_thread: - ${render_reply('')} - ${render_edit('')} - % endif -
    -
    -
    - % if show_comments: -
    - ${render_comments(thread['children'])} -
    - % endif -
    - - -<%def name="render_comments(comments)"> - % for comment in comments: -
    -
    -
    - ${comment['body']} -
    - ${render_info(comment)} - ${render_reply('')} - ${render_edit('')} -
    -
    -
    -
    - ${render_comments(comment['children'])} -
    -
    - % endfor - - -<%def name="render_info(content)"> - ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} - - -<%def name="render_reply(url)"> - Reply - - -<%def name="render_edit(url)"> - Edit - diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html index 4a009c10cb..28b17ff477 100644 --- a/lms/templates/discussion/single_thread.html +++ b/lms/templates/discussion/single_thread.html @@ -1,61 +1,6 @@ -<%! from django.core.urlresolvers import reverse %> +<%namespace name="renderer" file="thread.html"/>
    Discussion - ${render_thread(thread, edit_thread=True, show_comments=True)} + ${renderer.render_thread(thread, edit_thread=True, show_comments=True)}
    - -<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> -
    -
    - ${thread['title']} -
    -
    ${thread['body']}
    -
    - ${render_info(thread)} - % if edit_thread: - ${render_reply('')} - ${render_edit('')} - % endif -
    -
    -
    - % if show_comments: -
    - ${render_comments(thread['children'])} -
    - % endif -
    - - -<%def name="render_comments(comments)"> - % for comment in comments: -
    -
    -
    - ${comment['body']} -
    - ${render_info(comment)} - ${render_reply('')} - ${render_edit('')} -
    -
    -
    -
    - ${render_comments(comment['children'])} -
    -
    - % endfor - - -<%def name="render_info(content)"> - ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} - - -<%def name="render_reply(url)"> - Reply - - -<%def name="render_edit(url)"> - Edit - diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html new file mode 100644 index 0000000000..daaffb2939 --- /dev/null +++ b/lms/templates/discussion/thread.html @@ -0,0 +1,90 @@ +<%! from django.core.urlresolvers import reverse %> +<%! from datehelper import time_ago_in_words %> +<%! from dateutil.parser import parse %> + + + +<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> + <% + if show_comments: + url_for_thread = "" + else: + thread_id = thread['id'] + url_for_thread = reverse('django_comment_client.forum.views.single_thread', args=[thread_id]) + %> +
    +
    + ${render_vote(thread)} +
    + ${thread['title']} +
    +
    ${thread['body']}
    +
    + ${render_info(thread)} + % if edit_thread: + ${render_reply()} + ${render_edit()} + ${render_watch_thread()} + % endif +
    +
    +
    +
    + % if show_comments: +
    + ${render_comments(thread['children'])} +
    + % endif +
    + + +<%def name="render_comments(comments)"> + % for comment in comments: +
    +
    + ${render_vote(comment)} +
    +
    + ${comment['body']} +
    + ${render_info(comment)} + ${render_reply()} + ${render_edit()} +
    +
    +
    +
    +
    + ${render_comments(comment['children'])} +
    +
    + % endfor + + +<%def name="render_info(content)"> + ${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']} + + +<%def name="render_reply()"> + Reply + + +<%def name="render_edit()"> + Edit + + +<%def name="render_watch_thread()"> + Watch + + + +<%def name="render_vote(content)"> + <% + upvote = "˄" + downvote = "˅" + %> + + From 0ee1b0c5d04fe4ffbe0fe803b64b9d24ebce4fb4 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 19:24:28 -0400 Subject: [PATCH 026/352] removed unnecessary args --- lms/djangoapps/django_comment_client/forum/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index f6f893705e..e32fda2f5a 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -91,8 +91,6 @@ def render_accordion(request, course, discussion_info, discussion_id): def render_discussion(request, threads, discussion_id=None, search_text=''): context = { 'threads': threads, - 'time_ago_in_words': time_ago_in_words, - 'parse': dateutil.parser.parse, 'discussion_id': discussion_id, 'search_bar': render_search_bar(request, discussion_id, text=search_text), } @@ -139,8 +137,6 @@ def forum_form_discussion(request, course_id, discussion_id): def render_single_thread(request, thread_id): context = { 'thread': comment_client.get_thread(thread_id, recursive=True), - 'time_ago_in_words': time_ago_in_words, - 'parse': dateutil.parser.parse, } return render_to_string('discussion/single_thread.html', context) From 4b8c3b9b127f91734b79a230fb290b62f0278576 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 15:34:17 -0400 Subject: [PATCH 027/352] watch/unwatch threads; display current vote --- .../django_comment_client/base/urls.py | 13 ++- .../django_comment_client/base/views.py | 41 ++++++++- .../django_comment_client/forum/views.py | 13 ++- lms/static/coffee/src/discussion.coffee | 91 +++++++++++++++---- lms/static/sass/_discussion.scss | 3 + lms/templates/discussion/index.html | 5 +- lms/templates/discussion/thread.html | 3 - 7 files changed, 134 insertions(+), 35 deletions(-) diff --git a/lms/djangoapps/django_comment_client/base/urls.py b/lms/djangoapps/django_comment_client/base/urls.py index ec3ba7c625..a22ec401c3 100644 --- a/lms/djangoapps/django_comment_client/base/urls.py +++ b/lms/djangoapps/django_comment_client/base/urls.py @@ -2,16 +2,23 @@ from django.conf.urls.defaults import url, patterns import django_comment_client.base.views urlpatterns = patterns('django_comment_client.base.views', - url(r'(?P[\w\-]+)/threads/create$', 'create_thread', name='create_thread'), + url(r'threads/(?P[\w\-]+)/update$', 'update_thread', name='update_thread'), url(r'threads/(?P[\w\-]+)/reply$', 'create_comment', name='create_comment'), url(r'threads/(?P[\w\-]+)/delete', 'delete_thread', name='delete_thread'), + url(r'threads/(?P[\w\-]+)/upvote$', 'vote_for_thread', {'value': 'up'}, name='upvote_thread'), + url(r'threads/(?P[\w\-]+)/downvote$', 'vote_for_thread', {'value': 'down'}, name='downvote_thread'), + url(r'threads/(?P[\w\-]+)/watch$', 'watch_thread', name='watch_thread'), + url(r'threads/(?P[\w\-]+)/unwatch$', 'unwatch_thread', name='unwatch_thread'), + url(r'comments/(?P[\w\-]+)/update$', 'update_comment', name='update_comment'), url(r'comments/(?P[\w\-]+)/endorse$', 'endorse_comment', name='endorse_comment'), url(r'comments/(?P[\w\-]+)/reply$', 'create_sub_comment', name='create_sub_comment'), url(r'comments/(?P[\w\-]+)/delete$', 'delete_comment', name='delete_comment'), url(r'comments/(?P[\w\-]+)/upvote$', 'vote_for_comment', {'value': 'up'}, name='upvote_comment'), url(r'comments/(?P[\w\-]+)/downvote$', 'vote_for_comment', {'value': 'down'}, name='downvote_comment'), - url(r'threads/(?P[\w\-]+)/upvote$', 'vote_for_thread', {'value': 'up'}, name='upvote_thread'), - url(r'threads/(?P[\w\-]+)/downvote$', 'vote_for_thread', {'value': 'down'}, name='downvote_thread'), + + url(r'(?P[\w\-]+)/threads/create$', 'create_thread', name='create_thread'), + url(r'(?P[\w\-]+)/watch$', 'watch_commentable', name='watch_commentable'), + url(r'(?P[\w\-]+)/unwatch$', 'unwatch_commentable', name='unwatch_commentable'), ) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 291bb4c732..518751162d 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -129,8 +129,47 @@ def vote_for_thread(request, thread_id, value): response = comment_client.vote_for_thread(thread_id, user_id, value) return JsonResponse(response) -#undo vote: disabled for now +@login_required +@require_POST +def watch_thread(request, thread_id): + user_id = request.user.id + response = comment_client.subscribe_thread(user_id, thread_id) + return JsonResponse(response) +@login_required +@require_POST +def watch_commentable(request, commentable_id): + user_id = request.user.id + response = comment_client.subscribe_commentable(user_id, commentable_id) + return JsonResponse(response) + +@login_required +@require_POST +def follow(request, followed_user_id): + user_id = request.user.id + response = comment_client.follow(user_id, followed_user_id) + return JsonResponse(response) + +@login_required +@require_POST +def unwatch_thread(request, thread_id): + user_id = request.user.id + response = comment_client.unsubscribe_thread(user_id, thread_id) + return JsonResponse(response) + +@login_required +@require_POST +def unwatch_commentable(request, commentable_id): + user_id = request.user.id + response = comment_client.unsubscribe_commentable(user_id, commentable_id) + return JsonResponse(response) + +@login_required +@require_POST +def unfollow(request, followed_user_id): + user_id = request.user.id + response = comment_client.unfollow(user_id, followed_user_id) + return JsonResponse(response) @login_required @require_GET diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index e32fda2f5a..a7502bac49 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -22,6 +22,7 @@ from datehelper import time_ago_in_words import operator import itertools +import json _FULLMODULES = None _DISCUSSIONINFO = None @@ -80,11 +81,12 @@ def get_categorized_discussion_info(request, user, course, course_name, url_cour return _DISCUSSIONINFO def render_accordion(request, course, discussion_info, discussion_id): - context = dict([ - ('course', course), - ('discussion_info', discussion_info), - ('active', discussion_id), # TODO change this later - ('csrf', csrf(request)['csrf_token'])]) + context = { + 'course': course, + 'discussion_info': discussion_info, + 'active': discussion_id, + 'csrf': csrf(request)['csrf_token'], + } return render_to_string('discussion/accordion.html', context) @@ -147,6 +149,7 @@ def single_thread(request, thread_id): 'init': '', 'content': render_single_thread(request, thread_id), 'accordion': '', + 'user_info': json.dumps(comment_client.get_user_info(request.user.id)), } return render_to_response('discussion/index.html', context) diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index b3ee41203c..fe0007a8f0 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -9,38 +9,93 @@ $ -> $('#open_close_accordion a').click @toggle $('#accordion').show() - $("section.discussion").each (index, discussion) -> Discussion.bindDiscussionEvents(discussion) + Discussion.initializeDiscussion(discussion) + +generateLocal = (elem) -> + (selector) -> $(elem).find(selector) + +generateDiscussionLink = (cls, txt, handler) -> + $("").addClass("discussion-link"). + attr("href", "javascript:void(0)"). + addClass(cls).html(txt). + click(-> handler(this)) Discussion = urlFor: (name, param) -> { - create_thread : "/discussions/#{param}/threads/create" - update_thread : "/discussions/threads/#{param}/update" - create_comment : "/discussions/threads/#{param}/reply" - delete_thread : "/discussions/threads/#{param}/delete" - update_comment : "/discussions/comments/#{param}/update" - endorse_comment : "/discussions/comments/#{param}/endorse" - create_sub_comment : "/discussions/comments/#{param}/reply" - delete_comment : "/discussions/comments/#{param}/delete" - upvote_comment : "/discussions/comments/#{param}/upvote" - downvote_comment : "/discussions/comments/#{param}/downvote" - upvote_thread : "/discussions/threads/#{param}/upvote" - downvote_thread : "/discussions/threads/#{param}/downvote" - search : "/discussions/forum/search" + watch_commentable : "/discussions/#{param}/watch" + unwatch_commentable : "/discussions/#{param}/unwatch" + create_thread : "/discussions/#{param}/threads/create" + update_thread : "/discussions/threads/#{param}/update" + create_comment : "/discussions/threads/#{param}/reply" + delete_thread : "/discussions/threads/#{param}/delete" + upvote_thread : "/discussions/threads/#{param}/upvote" + downvote_thread : "/discussions/threads/#{param}/downvote" + watch_thread : "/discussions/threads/#{param}/watch" + unwatch_thread : "/discussions/threads/#{param}/unwatch" + update_comment : "/discussions/comments/#{param}/update" + endorse_comment : "/discussions/comments/#{param}/endorse" + create_sub_comment : "/discussions/comments/#{param}/reply" + delete_comment : "/discussions/comments/#{param}/delete" + upvote_comment : "/discussions/comments/#{param}/upvote" + downvote_comment : "/discussions/comments/#{param}/downvote" + search : "/discussions/forum/search" }[name] handleAnchorAndReload: (response) -> #window.location = window.location.pathname + "#" + response['id'] window.location.reload() + initializeDiscussion: (discussion) -> + initializeVote = (index, content) -> + $content = $(content) + $local = generateLocal($content.children(".discussion-content")) + id = $content.attr("_id") + if id in user_info.upvoted_ids + $local(".discussion-vote-up").addClass("voted") + else if id in user_info.downvoted_ids + $local(".discussion-vote-down").addClass("voted") + + + initializeWatchThreads = (index, thread) -> + $thread = $(thread) + id = $thread.attr("_id") + $local = generateLocal($thread.children(".discussion-content")) + + handleWatchThread = (elem) -> + url = Discussion.urlFor('watch_thread', id) + console.log url + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + handleUnwatchThread = (elem) -> + url = Discussion.urlFor('unwatch_thread', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + if id in user_info.subscribed_thread_ids + unwatchThread = generateDiscussionLink("discussion-unwatch-thread", "Unwatch", handleUnwatchThread) + $local(".info").append(unwatchThread) + else + watchThread = generateDiscussionLink("discussion-watch-thread", "Watch", handleWatchThread) + $local(".info").append(watchThread) + + if user_info? + $(discussion).find(".comment").each(initializeVote) + $(discussion).find(".thread").each(initializeVote).each(initializeWatchThreads) + bindContentEvents: (content) -> $content = $(content) $discussionContent = $content.children(".discussion-content") - $local = (selector) -> $discussionContent.find(selector) + $local = generateLocal($discussionContent) discussionContentHoverIn = -> status = $discussionContent.attr("status") || "normal" @@ -58,11 +113,7 @@ Discussion = $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) - generateDiscussionLink = (cls, txt, handler) -> - $("").addClass("discussion-link"). - attr("href", "javascript:void(0)"). - addClass(cls).html(txt). - click(-> handler(this)) + handleReply = (elem) -> editView = $local(".discussion-content-edit") diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index a5c2b5ed4f..724d7b15cf 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -66,6 +66,9 @@ $discussion_input_width: 60%; &.discussion-vote-down { margin-top: 3px; } + &.voted { + color: #1d9dd9; + } } } .discussion-right-wrapper { diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index d150dc7ddf..c970bfbcf4 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -8,11 +8,10 @@ <%block name="js_extra"> - + ##<%include file="../course_navigation.html" args="active_page='discussion'" /> diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html index daaffb2939..f76d3de8df 100644 --- a/lms/templates/discussion/thread.html +++ b/lms/templates/discussion/thread.html @@ -2,8 +2,6 @@ <%! from datehelper import time_ago_in_words %> <%! from dateutil.parser import parse %> - - <%def name="render_thread(thread, edit_thread=False, show_comments=False)"> <% if show_comments: @@ -24,7 +22,6 @@ % if edit_thread: ${render_reply()} ${render_edit()} - ${render_watch_thread()} % endif
    From b2da6eef9f50e15af9f4f6340f553ed1df43dd8b Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 16:49:27 -0400 Subject: [PATCH 028/352] provide user_id for xmodules --- lms/djangoapps/courseware/module_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 3f111c2953..5f9fc8da82 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -131,6 +131,7 @@ def get_module(user, request, location, student_module_cache, position=None): shared_module = None instance_state = instance_module.state if instance_module is not None else None + instance_state = json.dumps(dict(json.loads(instance_state).items() + [("user_id", user.id)])) shared_state = shared_module.state if shared_module is not None else None # TODO (vshnayder): fix hardcoded urls (use reverse) From e6d437b66edf3a990f31bf4e944c55df67feae91 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 16:49:53 -0400 Subject: [PATCH 029/352] get user_id in discussion module --- common/lib/xmodule/xmodule/discussion_module.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index e1c41beeb6..ffc0597b19 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -8,6 +8,8 @@ import dateutil from dateutil.tz import tzlocal from datehelper import time_ago_in_words +import json + class DiscussionModule(XModule): def get_html(self): context = { @@ -16,15 +18,20 @@ class DiscussionModule(XModule): 'parse': dateutil.parser.parse, 'discussion_id': self.discussion_id, 'search_bar': '', + 'user_info': comment_client.get_user_info(self.user_id, raw=True), } return self.system.render_template('discussion/inline.html', context) def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) + + if isinstance(instance_state, str): + instance_state = json.loads(instance_state) xml_data = etree.fromstring(definition['data']) self.discussion_id = xml_data.attrib['id'] self.title = xml_data.attrib['for'] self.category = xml_data.attrib['category'] + self.user_id = instance_state['user_id'] class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule From 6a816e453f97b85b9fabfdb4c81a61607071c17c Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 16:50:21 -0400 Subject: [PATCH 030/352] watch/unwatch, vote status enabled in all views --- lms/djangoapps/django_comment_client/forum/views.py | 4 +++- lms/templates/discussion/index.html | 4 ---- lms/templates/discussion/inline.html | 5 +++++ lms/templates/discussion/single_thread.html | 5 +++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index a7502bac49..dfff24e5bf 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -95,6 +95,7 @@ def render_discussion(request, threads, discussion_id=None, search_text=''): 'threads': threads, 'discussion_id': discussion_id, 'search_bar': render_search_bar(request, discussion_id, text=search_text), + 'user_info': comment_client.get_user_info(request.user.id, raw=True), } return render_to_string('discussion/inline.html', context) @@ -139,6 +140,7 @@ def forum_form_discussion(request, course_id, discussion_id): def render_single_thread(request, thread_id): context = { 'thread': comment_client.get_thread(thread_id, recursive=True), + 'user_info': comment_client.get_user_info(request.user.id, raw=True), } return render_to_string('discussion/single_thread.html', context) @@ -149,7 +151,7 @@ def single_thread(request, thread_id): 'init': '', 'content': render_single_thread(request, thread_id), 'accordion': '', - 'user_info': json.dumps(comment_client.get_user_info(request.user.id)), + 'user_info': comment_client.get_user_info(request.user.id, raw=True), } return render_to_response('discussion/index.html', context) diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index c970bfbcf4..7b35863377 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -8,10 +8,6 @@ <%block name="js_extra"> - - ##<%include file="../course_navigation.html" args="active_page='discussion'" /> diff --git a/lms/templates/discussion/inline.html b/lms/templates/discussion/inline.html index e4f0ca43d3..d9d5bb8df1 100644 --- a/lms/templates/discussion/inline.html +++ b/lms/templates/discussion/inline.html @@ -14,3 +14,8 @@ ${renderer.render_thread(thread, edit_thread=False, show_comments=False)} % endfor + + + diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html index 28b17ff477..0545b2ba4a 100644 --- a/lms/templates/discussion/single_thread.html +++ b/lms/templates/discussion/single_thread.html @@ -4,3 +4,8 @@ Discussion ${renderer.render_thread(thread, edit_thread=True, show_comments=True)} + + + From aab9f8dc1c9e5055b53c7f0781aaf6587ca2eb78 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Tue, 24 Jul 2012 18:51:56 -0400 Subject: [PATCH 031/352] make course id available in xmodule --- .../lib/xmodule/xmodule/discussion_module.py | 4 +- lms/djangoapps/courseware/module_render.py | 15 +++- .../django_comment_client/base/views.py | 38 +++++----- .../django_comment_client/forum/urls.py | 2 +- .../django_comment_client/forum/views.py | 30 +++++--- lms/static/coffee/src/discussion.coffee | 69 +++++++++++++------ lms/static/sass/_discussion.scss | 17 +++++ lms/templates/course_navigation.html | 2 +- lms/templates/discussion/accordion.html | 18 ++--- lms/templates/discussion/index.html | 2 +- lms/templates/discussion/inline.html | 12 ++-- lms/templates/discussion/search_bar.html | 2 +- lms/templates/discussion/single_thread.html | 6 +- lms/templates/discussion/thread.html | 4 +- lms/urls.py | 6 +- 15 files changed, 147 insertions(+), 80 deletions(-) diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index ffc0597b19..24ba4ad206 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -19,6 +19,7 @@ class DiscussionModule(XModule): 'discussion_id': self.discussion_id, 'search_bar': '', 'user_info': comment_client.get_user_info(self.user_id, raw=True), + 'course_id': self.course_id, } return self.system.render_template('discussion/inline.html', context) @@ -30,8 +31,9 @@ class DiscussionModule(XModule): xml_data = etree.fromstring(definition['data']) self.discussion_id = xml_data.attrib['id'] self.title = xml_data.attrib['for'] - self.category = xml_data.attrib['category'] + self.discussion_category = xml_data.attrib['discussion_category'] self.user_id = instance_state['user_id'] + self.course_id = instance_state['course_id'] class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 5f9fc8da82..ad85bfbf4f 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -122,6 +122,13 @@ def get_module(user, request, location, student_module_cache, position=None): ''' descriptor = modulestore().get_item(location) + user_id = user.id + + import re + course_id = re.search(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/', request.path) + if course_id: + course_id = course_id.group('course_id') + instance_module = student_module_cache.lookup(descriptor.category, descriptor.location.url()) shared_state_key = getattr(descriptor, 'shared_state_key', None) @@ -130,8 +137,12 @@ def get_module(user, request, location, student_module_cache, position=None): else: shared_module = None - instance_state = instance_module.state if instance_module is not None else None - instance_state = json.dumps(dict(json.loads(instance_state).items() + [("user_id", user.id)])) + instance_state = instance_module.state if instance_module is not None else {} + + instance_hash = json.loads(instance_state) if isinstance(instance_state, str) or isinstance(instance_state, unicode) \ + else instance_state + + instance_state = json.dumps(dict(instance_hash.items() + [("user_id", user.id), ("course_id", course_id)])) shared_state = shared_module.state if shared_module is not None else None # TODO (vshnayder): fix hardcoded urls (use reverse) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 518751162d..a148d80020 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -52,41 +52,41 @@ def extract(dic, keys): @login_required @require_POST -def create_thread(request, commentable_id): +def create_thread(request, course_id, commentable_id): attributes = extract(request.POST, ['body', 'title']) attributes['user_id'] = request.user.id - attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + attributes['course_id'] = course_id response = comment_client.create_thread(commentable_id, attributes) return JsonResponse(response) @thread_author_only @login_required @require_POST -def update_thread(request, thread_id): +def update_thread(request, course_id, thread_id): attributes = extract(request.POST, ['body', 'title']) response = comment_client.update_thread(thread_id, attributes) return JsonResponse(response) @login_required @require_POST -def create_comment(request, thread_id): +def create_comment(request, course_id, thread_id): attributes = extract(request.POST, ['body']) attributes['user_id'] = request.user.id - attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + attributes['course_id'] = course_id response = comment_client.create_comment(thread_id, attributes) return JsonResponse(response) @thread_author_only @login_required @require_POST -def delete_thread(request, thread_id): +def delete_thread(request, course_id, thread_id): response = comment_client.delete_thread(thread_id) return JsonResponse(response) @thread_author_only @login_required @require_POST -def update_comment(request, comment_id): +def update_comment(request, course_id, comment_id): attributes = extract(request.POST, ['body']) response = comment_client.update_comment(comment_id, attributes) return JsonResponse(response) @@ -94,14 +94,14 @@ def update_comment(request, comment_id): @instructor_only @login_required @require_POST -def endorse_comment(request, comment_id): +def endorse_comment(request, course_id, comment_id): attributes = extract(request.POST, ['endorsed']) response = comment_client.update_comment(comment_id, attributes) return JsonResponse(response) @login_required @require_POST -def create_sub_comment(request, comment_id): +def create_sub_comment(request, course_id, comment_id): attributes = extract(request.POST, ['body']) attributes['user_id'] = request.user.id attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow @@ -111,69 +111,69 @@ def create_sub_comment(request, comment_id): @comment_author_only @login_required @require_POST -def delete_comment(request, comment_id): +def delete_comment(request, course_id, comment_id): response = comment_client.delete_comment(comment_id) return JsonResponse(response) @login_required @require_POST -def vote_for_comment(request, comment_id, value): +def vote_for_comment(request, course_id, comment_id, value): user_id = request.user.id response = comment_client.vote_for_comment(comment_id, user_id, value) return JsonResponse(response) @login_required @require_POST -def vote_for_thread(request, thread_id, value): +def vote_for_thread(request, course_id, thread_id, value): user_id = request.user.id response = comment_client.vote_for_thread(thread_id, user_id, value) return JsonResponse(response) @login_required @require_POST -def watch_thread(request, thread_id): +def watch_thread(request, course_id, thread_id): user_id = request.user.id response = comment_client.subscribe_thread(user_id, thread_id) return JsonResponse(response) @login_required @require_POST -def watch_commentable(request, commentable_id): +def watch_commentable(request, course_id, commentable_id): user_id = request.user.id response = comment_client.subscribe_commentable(user_id, commentable_id) return JsonResponse(response) @login_required @require_POST -def follow(request, followed_user_id): +def follow(request, course_id, followed_user_id): user_id = request.user.id response = comment_client.follow(user_id, followed_user_id) return JsonResponse(response) @login_required @require_POST -def unwatch_thread(request, thread_id): +def unwatch_thread(request, course_id, thread_id): user_id = request.user.id response = comment_client.unsubscribe_thread(user_id, thread_id) return JsonResponse(response) @login_required @require_POST -def unwatch_commentable(request, commentable_id): +def unwatch_commentable(request, course_id, commentable_id): user_id = request.user.id response = comment_client.unsubscribe_commentable(user_id, commentable_id) return JsonResponse(response) @login_required @require_POST -def unfollow(request, followed_user_id): +def unfollow(request, course_id, followed_user_id): user_id = request.user.id response = comment_client.unfollow(user_id, followed_user_id) return JsonResponse(response) @login_required @require_GET -def search(request): +def search(request, course_id): text = request.GET.get('text', None) commentable_id = request.GET.get('commentable_id', None) response = comment_client.search(text, commentable_id) diff --git a/lms/djangoapps/django_comment_client/forum/urls.py b/lms/djangoapps/django_comment_client/forum/urls.py index 3a724ebee4..0fd4f0e320 100644 --- a/lms/djangoapps/django_comment_client/forum/urls.py +++ b/lms/djangoapps/django_comment_client/forum/urls.py @@ -4,5 +4,5 @@ import django_comment_client.forum.views urlpatterns = patterns('django_comment_client.forum.views', url(r'search$', 'search', name='search'), url(r'threads/(?P\w+)$', 'single_thread', name='single_thread'), - url(r'(?P[\w\.\-]+)/(?P\w+)$', 'forum_form_discussion', name='forum_form_discussion'), + url(r'(?P\w+)$', 'forum_form_discussion', name='forum_form_discussion'), ) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index dfff24e5bf..c2a136d6cf 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -58,7 +58,7 @@ def get_categorized_discussion_info(request, user, course, course_name, url_cour return { 'title': module.title, 'discussion_id': module.discussion_id, - 'category': module.category, + 'category': module.discussion_category, } discussion_module_descriptors = map(_get_module_descriptor, @@ -90,27 +90,28 @@ def render_accordion(request, course, discussion_info, discussion_id): return render_to_string('discussion/accordion.html', context) -def render_discussion(request, threads, discussion_id=None, search_text=''): +def render_discussion(request, course_id, threads, discussion_id=None, search_text=''): context = { 'threads': threads, 'discussion_id': discussion_id, - 'search_bar': render_search_bar(request, discussion_id, text=search_text), + 'search_bar': render_search_bar(request, course_id, discussion_id, text=search_text), 'user_info': comment_client.get_user_info(request.user.id, raw=True), + 'course_id': course_id, } return render_to_string('discussion/inline.html', context) -def render_search_bar(request, discussion_id=None, text=''): +def render_search_bar(request, course_id, discussion_id=None, text=''): if not discussion_id: return '' context = { 'discussion_id': discussion_id, 'text': text, + 'course_id': course_id, } return render_to_string('discussion/search_bar.html', context) def forum_form_discussion(request, course_id, discussion_id): - course_id = course_id.replace('-', '/') course = check_course(course_id) _, course_name, _ = course_id.split('/') @@ -131,39 +132,46 @@ def forum_form_discussion(request, course_id, discussion_id): 'COURSE_TITLE': course.title, 'course': course, 'init': '', - 'content': render_discussion(request, threads, discussion_id, search_text), + 'content': render_discussion(request, course_id, threads, discussion_id, search_text), 'accordion': render_accordion(request, course, discussion_info, discussion_id), } return render_to_response('discussion/index.html', context) -def render_single_thread(request, thread_id): +def render_single_thread(request, course_id, thread_id): context = { 'thread': comment_client.get_thread(thread_id, recursive=True), 'user_info': comment_client.get_user_info(request.user.id, raw=True), + 'course_id': course_id, } return render_to_string('discussion/single_thread.html', context) -def single_thread(request, thread_id): +def single_thread(request, course_id, thread_id): + + course = check_course(course_id) context = { 'csrf': csrf(request)['csrf_token'], 'init': '', - 'content': render_single_thread(request, thread_id), + 'content': render_single_thread(request, course_id, thread_id), 'accordion': '', 'user_info': comment_client.get_user_info(request.user.id, raw=True), + 'course': course, } return render_to_response('discussion/index.html', context) -def search(request): +def search(request, course_id): + + course = check_course(course_id) text = request.GET.get('text', None) threads = comment_client.search(text) context = { 'csrf': csrf(request)['csrf_token'], 'init': '', - 'content': render_discussion(request, threads, search_text=text), + 'content': render_discussion(request, course_id, threads, search_text=text), 'accordion': '', + 'course': course, } return render_to_response('discussion/index.html', context) diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index fe0007a8f0..44aebb7241 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -26,23 +26,23 @@ Discussion = urlFor: (name, param) -> { - watch_commentable : "/discussions/#{param}/watch" - unwatch_commentable : "/discussions/#{param}/unwatch" - create_thread : "/discussions/#{param}/threads/create" - update_thread : "/discussions/threads/#{param}/update" - create_comment : "/discussions/threads/#{param}/reply" - delete_thread : "/discussions/threads/#{param}/delete" - upvote_thread : "/discussions/threads/#{param}/upvote" - downvote_thread : "/discussions/threads/#{param}/downvote" - watch_thread : "/discussions/threads/#{param}/watch" - unwatch_thread : "/discussions/threads/#{param}/unwatch" - update_comment : "/discussions/comments/#{param}/update" - endorse_comment : "/discussions/comments/#{param}/endorse" - create_sub_comment : "/discussions/comments/#{param}/reply" - delete_comment : "/discussions/comments/#{param}/delete" - upvote_comment : "/discussions/comments/#{param}/upvote" - downvote_comment : "/discussions/comments/#{param}/downvote" - search : "/discussions/forum/search" + watch_commentable : "/courses/#{$$course_id}/discussion/#{param}/watch" + unwatch_commentable : "/courses/#{$$course_id}/discussion/#{param}/unwatch" + create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create" + update_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/update" + create_comment : "/courses/#{$$course_id}/discussion/threads/#{param}/reply" + delete_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/delete" + upvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/upvote" + downvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/downvote" + watch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/watch" + unwatch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unwatch" + update_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/update" + endorse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/endorse" + create_sub_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/reply" + delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete" + upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote" + downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote" + search : "/courses/#{$$course_id}/discussion/forum/search" }[name] handleAnchorAndReload: (response) -> @@ -54,11 +54,36 @@ Discussion = $content = $(content) $local = generateLocal($content.children(".discussion-content")) id = $content.attr("_id") - if id in user_info.upvoted_ids + if id in $$user_info.upvoted_ids $local(".discussion-vote-up").addClass("voted") - else if id in user_info.downvoted_ids + else if id in $$user_info.downvoted_ids $local(".discussion-vote-down").addClass("voted") + initializeWatchDiscussion = (discussion) -> + $discussion = $(discussion) + id = $discussion.attr("_id") + $local = generateLocal($discussion.children(".discussion-non-content")) + + handleWatchDiscussion = (elem) -> + url = Discussion.urlFor('watch_commentable', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + handleUnwatchDiscussion = (elem) -> + url = Discussion.urlFor('unwatch_commentable', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + if id in $$user_info.subscribed_commentable_ids + unwatchDiscussion = generateDiscussionLink("discussion-unwatch-discussion", "Unwatch", handleUnwatchDiscussion) + $local(".discussion-title-wrapper").append(unwatchDiscussion) + else + watchDiscussion = generateDiscussionLink("discussion-watch-discussion", "Watch", handleWatchDiscussion) + $local(".discussion-title-wrapper").append(watchDiscussion) initializeWatchThreads = (index, thread) -> $thread = $(thread) @@ -67,7 +92,6 @@ Discussion = handleWatchThread = (elem) -> url = Discussion.urlFor('watch_thread', id) - console.log url $.post url, {}, (response, textStatus) -> if textStatus == "success" Discussion.handleAnchorAndReload(response) @@ -80,16 +104,17 @@ Discussion = Discussion.handleAnchorAndReload(response) , 'json' - if id in user_info.subscribed_thread_ids + if id in $$user_info.subscribed_thread_ids unwatchThread = generateDiscussionLink("discussion-unwatch-thread", "Unwatch", handleUnwatchThread) $local(".info").append(unwatchThread) else watchThread = generateDiscussionLink("discussion-watch-thread", "Watch", handleWatchThread) $local(".info").append(watchThread) - if user_info? + if $$user_info? $(discussion).find(".comment").each(initializeVote) $(discussion).find(".thread").each(initializeVote).each(initializeWatchThreads) + initializeWatchDiscussion(discussion) bindContentEvents: (content) -> diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 724d7b15cf..cf669a10c2 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -37,6 +37,7 @@ $discussion_input_width: 60%; @include discussion-font; } } + .discussion-title { @include discussion-font; @include discussion-clickable; @@ -45,6 +46,22 @@ $discussion_input_width: 60%; margin-bottom: 20px; display: block; } + .discussion-title-wrapper { + .discussion-watch-discussion, .discussion-unwatch-discussion { + display: none; + @include discussion-font; + margin-left: 5px; + font-size: $comment_info_size; + } + .discussion-title { + display: inline-block; + } + &:hover { + .discussion-watch-discussion, .discussion-unwatch-discussion { + display: inline-block; + } + } + } .discussion-votes { margin-right: 8px; margin-top: 5px; diff --git a/lms/templates/course_navigation.html b/lms/templates/course_navigation.html index beaea5e2a7..4682fc423f 100644 --- a/lms/templates/course_navigation.html +++ b/lms/templates/course_navigation.html @@ -15,7 +15,7 @@ def url_class(url):
  • Course Info
  • % if user.is_authenticated():
  • Textbook
  • -
  • Discussion
  • +
  • Discussion
  • % endif
  • Wiki
  • % if user.is_authenticated(): diff --git a/lms/templates/discussion/accordion.html b/lms/templates/discussion/accordion.html index 99c72a8f96..c072705d55 100644 --- a/lms/templates/discussion/accordion.html +++ b/lms/templates/discussion/accordion.html @@ -1,23 +1,23 @@ <%! from django.core.urlresolvers import reverse %> <% -def url_for(board): - return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id.replace('/', '-'), board['discussion_id']]) +def url_for(commentable): + return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id, commentable['discussion_id']]) %> -<%def name="make_category(category, boards)"> +<%def name="make_category(category, commentables)">

    ${category}

    -% for category, boards in discussion_info.items(): - ${make_category(category, boards)} +% for category, commentables in discussion_info.items(): + ${make_category(category, commentables)} % endfor diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index 7b35863377..2beac5ddd9 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -10,7 +10,7 @@ <%block name="js_extra"> -##<%include file="../course_navigation.html" args="active_page='discussion'" /> +<%include file="../course_navigation.html" args="active_page='discussion'" />
    diff --git a/lms/templates/discussion/inline.html b/lms/templates/discussion/inline.html index d9d5bb8df1..779a08e6f6 100644 --- a/lms/templates/discussion/inline.html +++ b/lms/templates/discussion/inline.html @@ -1,8 +1,10 @@ <%namespace name="renderer" file="thread.html"/> -
    +
    - Discussion + ${search_bar}
    @@ -11,11 +13,11 @@
    % for thread in threads: - ${renderer.render_thread(thread, edit_thread=False, show_comments=False)} + ${renderer.render_thread(course_id, thread, edit_thread=False, show_comments=False)} % endfor
    - diff --git a/lms/templates/discussion/search_bar.html b/lms/templates/discussion/search_bar.html index dfc6ab5e2f..bf3ec6a02f 100644 --- a/lms/templates/discussion/search_bar.html +++ b/lms/templates/discussion/search_bar.html @@ -2,7 +2,7 @@ <% def url_for_search(): - return reverse('django_comment_client.forum.views.search') + return reverse('django_comment_client.forum.views.search', args=[course_id]) %>
    diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html index 0545b2ba4a..3e320b68ca 100644 --- a/lms/templates/discussion/single_thread.html +++ b/lms/templates/discussion/single_thread.html @@ -2,10 +2,10 @@
    Discussion - ${renderer.render_thread(thread, edit_thread=True, show_comments=True)} + ${renderer.render_thread(course_id, thread, edit_thread=True, show_comments=True)}
    - diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html index f76d3de8df..fbfa0f19c8 100644 --- a/lms/templates/discussion/thread.html +++ b/lms/templates/discussion/thread.html @@ -2,13 +2,13 @@ <%! from datehelper import time_ago_in_words %> <%! from dateutil.parser import parse %> -<%def name="render_thread(thread, edit_thread=False, show_comments=False)"> +<%def name="render_thread(course_id, thread, edit_thread=False, show_comments=False)"> <% if show_comments: url_for_thread = "" else: thread_id = thread['id'] - url_for_thread = reverse('django_comment_client.forum.views.single_thread', args=[thread_id]) + url_for_thread = reverse('django_comment_client.forum.views.single_thread', args=[course_id, thread_id]) %>
    diff --git a/lms/urls.py b/lms/urls.py index 73b440d331..796aea5bb4 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -135,6 +135,10 @@ if settings.COURSEWARE_ENABLED: 'courseware.views.profile', name="profile"), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/profile/(?P[^/]*)/$', 'courseware.views.profile'), + + # discussion + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/discussion/', + include('django_comment_client.urls')), ) # Multicourse wiki @@ -156,8 +160,6 @@ if settings.ASKBOT_ENABLED: # url(r'^robots.txt$', include('robots.urls')), ) -# discussion -urlpatterns += (url(r'^discussions/', include('django_comment_client.urls')), ) if settings.DEBUG: ## Jasmine From 048deaa87f841d1c968f2f6ba589d88af415a831 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Wed, 25 Jul 2012 15:30:05 -0400 Subject: [PATCH 032/352] show news & options when submit comment --- lms/djangoapps/courseware/views.py | 30 +++++- .../django_comment_client/base/views.py | 11 ++- .../django_comment_client/forum/views.py | 70 +------------- lms/djangoapps/django_comment_client/utils.py | 93 +++++++++++++++++++ lms/static/coffee/src/discussion.coffee | 37 ++++++-- lms/static/sass/_discussion.scss | 3 +- lms/static/sass/_news.scss | 19 ++++ lms/static/sass/application.scss | 1 + lms/templates/course_navigation.html | 1 + lms/templates/discussion/thread.html | 31 ++++--- lms/templates/news.html | 21 +++++ lms/templates/notifications.html | 48 ++++++++++ lms/templates/profile.html | 4 +- lms/urls.py | 2 + 14 files changed, 278 insertions(+), 93 deletions(-) create mode 100644 lms/djangoapps/django_comment_client/utils.py create mode 100644 lms/static/sass/_news.scss create mode 100644 lms/templates/news.html create mode 100644 lms/templates/notifications.html diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index f273778a3c..4d0e5867da 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -4,6 +4,8 @@ import logging import urllib import itertools +from functools import partial + from django.conf import settings from django.core.context_processors import csrf from django.core.urlresolvers import reverse @@ -20,6 +22,7 @@ from module_render import toc_for_course, get_module, get_section from models import StudentModuleCache from student.models import UserProfile from multicourse import multicourse_settings +from django_comment_client.utils import get_discussion_title from util.cache import cache, cache_if_anonymous from student.models import UserTestGroup, CourseEnrollment @@ -27,6 +30,11 @@ from courseware import grades from courseware.courses import check_course from xmodule.modulestore.django import modulestore +import comment_client + + + + log = logging.getLogger("mitx.courseware") template_imports = {'urllib': urllib} @@ -259,7 +267,6 @@ def course_info(request, course_id): return render_to_response('info.html', {'course': course}) - @ensure_csrf_cookie @cache_if_anonymous def course_about(request, course_id): @@ -287,3 +294,24 @@ def university_profile(request, org_id): template_file = "university_profile/{0}.html".format(org_id).lower() return render_to_response(template_file, context) + +def render_notifications(request, course, notifications): + context = { + 'notifications': notifications, + 'get_discussion_title': partial(get_discussion_title, request=request, course=course), + 'course': course, + } + return render_to_string('notifications.html', context) + +@login_required +def news(request, course_id): + course = check_course(course_id) + + notifications = comment_client.get_notifications(request.user.id) + + context = { + 'course': course, + 'content': render_notifications(request, course, notifications), + } + + return render_to_response('news.html', context) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index a148d80020..85e76bad55 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -71,8 +71,11 @@ def update_thread(request, course_id, thread_id): @require_POST def create_comment(request, course_id, thread_id): attributes = extract(request.POST, ['body']) - attributes['user_id'] = request.user.id + if request.POST.get('anonymous', 'false').lower() == 'false': + attributes['user_id'] = request.user.id attributes['course_id'] = course_id + attributes['auto_subscribe'] = bool(request.POST.get('autowatch', False)) + print attributes response = comment_client.create_comment(thread_id, attributes) return JsonResponse(response) @@ -103,8 +106,10 @@ def endorse_comment(request, course_id, comment_id): @require_POST def create_sub_comment(request, course_id, comment_id): attributes = extract(request.POST, ['body']) - attributes['user_id'] = request.user.id - attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + if request.POST.get('anonymous', 'false').lower() == 'false': + attributes['user_id'] = request.user.id + attributes['course_id'] = course_id + attributes['auto_subscribe'] = bool(request.POST.get('autowatch', False)) response = comment_client.create_sub_comment(comment_id, attributes) return JsonResponse(response) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index c2a136d6cf..bde1ed7f51 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -6,80 +6,16 @@ from django.core.context_processors import csrf from mitxmako.shortcuts import render_to_response, render_to_string from courseware.courses import check_course -from courseware.models import StudentModuleCache -from courseware.module_render import get_module, get_section -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore - -from importlib import import_module - -from django.conf import settings import comment_client import dateutil from dateutil.tz import tzlocal from datehelper import time_ago_in_words -import operator -import itertools +from django_comment_client.utils import get_categorized_discussion_info + import json -_FULLMODULES = None -_DISCUSSIONINFO = None - -def get_full_modules(): - global _FULLMODULES - if not _FULLMODULES: - class_path = settings.MODULESTORE['default']['ENGINE'] - module_path, _, class_name = class_path.rpartition('.') - class_ = getattr(import_module(module_path), class_name) - modulestore = class_(eager=True, **settings.MODULESTORE['default']['OPTIONS']) - _FULLMODULES = modulestore.modules - return _FULLMODULES - -def get_categorized_discussion_info(request, user, course, course_name, url_course_id): - """ - return a dict of the form {category: modules} - """ - global _DISCUSSIONINFO - if not _DISCUSSIONINFO: - - _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ - and x[0].dict()['course'] == course_name - - _get_module_descriptor = operator.itemgetter(1) - - def _get_module(module_descriptor): - print module_descriptor - module = get_module(user, request, module_descriptor.location, student_module_cache)[0] - return module - - def _extract_info(module): - return { - 'title': module.title, - 'discussion_id': module.discussion_id, - 'category': module.discussion_category, - } - - discussion_module_descriptors = map(_get_module_descriptor, - filter(_is_course_discussion, - get_full_modules().items())) - - student_module_cache = StudentModuleCache(user, course) - - discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) - - _DISCUSSIONINFO = dict((category, list(l)) \ - for category, l in itertools.groupby(discussion_info, operator.itemgetter('category'))) - - _DISCUSSIONINFO['General'] = [{ - 'title': 'General discussion', - 'discussion_id': url_course_id, - 'category': 'General', - }] - - return _DISCUSSIONINFO - def render_accordion(request, course, discussion_info, discussion_id): context = { 'course': course, @@ -118,7 +54,7 @@ def forum_form_discussion(request, course_id, discussion_id): url_course_id = course_id.replace('/', '_').replace('.', '_') - discussion_info = get_categorized_discussion_info(request, request.user, course, course_name, url_course_id) + discussion_info = get_categorized_discussion_info(request, course)#request.user, course, course_name, url_course_id) search_text = request.GET.get('text', '') diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py new file mode 100644 index 0000000000..24f4f5115a --- /dev/null +++ b/lms/djangoapps/django_comment_client/utils.py @@ -0,0 +1,93 @@ +from importlib import import_module +from courseware.models import StudentModuleCache +from courseware.module_render import get_module +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore + +from django.conf import settings +import operator +import itertools + +_FULLMODULES = None +_DISCUSSIONINFO = None + +def get_full_modules(): + global _FULLMODULES + if not _FULLMODULES: + class_path = settings.MODULESTORE['default']['ENGINE'] + module_path, _, class_name = class_path.rpartition('.') + class_ = getattr(import_module(module_path), class_name) + modulestore = class_(eager=True, **settings.MODULESTORE['default']['OPTIONS']) + _FULLMODULES = modulestore.modules + return _FULLMODULES + +def get_categorized_discussion_info(request, course): + """ + return a dict of the form {category: modules} + """ + global _DISCUSSIONINFO + if not _DISCUSSIONINFO: + initialize_discussion_info(request, course) + return _DISCUSSIONINFO['categorized'] + +def get_discussion_title(request, course, discussion_id): + global _DISCUSSIONINFO + if not _DISCUSSIONINFO: + initialize_discussion_info(request, course) + title = _DISCUSSIONINFO['by_id'].get(discussion_id, {}).get('title', '(no title)') + if title == '(no title)': + print "title shouldn't be none" + import pdb; pdb.set_trace() + return title + +def initialize_discussion_info(request, course): + + global _DISCUSSIONINFO + if _DISCUSSIONINFO: + return + + course_id = course.id + _, course_name, _ = course_id.split('/') + user = request.user + url_course_id = course_id.replace('/', '_').replace('.', '_') + + _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ + and x[0].dict()['course'] == course_name + + _get_module_descriptor = operator.itemgetter(1) + + def _get_module(module_descriptor): + print module_descriptor + module = get_module(user, request, module_descriptor.location, student_module_cache)[0] + return module + + def _extract_info(module): + return { + 'title': module.title, + 'discussion_id': module.discussion_id, + 'category': module.discussion_category, + } + + def _pack_with_id(info): + return (info['discussion_id'], info) + + discussion_module_descriptors = map(_get_module_descriptor, + filter(_is_course_discussion, + get_full_modules().items())) + + student_module_cache = StudentModuleCache(user, course) + + discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) + + _DISCUSSIONINFO = {} + + _DISCUSSIONINFO['by_id'] = dict(map(_pack_with_id, discussion_info)) + + _DISCUSSIONINFO['categorized'] = dict((category, list(l)) \ + for category, l in itertools.groupby(discussion_info, operator.itemgetter('category'))) + + _DISCUSSIONINFO['categorized']['General'] = [{ + 'title': 'General discussion', + 'discussion_id': url_course_id, + 'category': 'General', + }] diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index 44aebb7241..51420f38cc 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -122,6 +122,8 @@ Discussion = $discussionContent = $content.children(".discussion-content") $local = generateLocal($discussionContent) + id = $content.attr("_id") + discussionContentHoverIn = -> status = $discussionContent.attr("status") || "normal" if status == "normal" @@ -138,15 +140,32 @@ Discussion = $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) - - handleReply = (elem) -> editView = $local(".discussion-content-edit") if editView.length editView.show() else editView = $("
    ").addClass("discussion-content-edit") - editView.append($(" New Post +
    +
    + +
    +
    % for thread in threads: diff --git a/lms/templates/main.html b/lms/templates/main.html index 7ef8960970..6f5f2aae59 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -14,6 +14,7 @@ <%static:js group='main_vendor'/> <%block name="headextra"/> + From 7103bd0071be947436dff18764ec51840b73c99c Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Wed, 25 Jul 2012 20:57:38 -0400 Subject: [PATCH 039/352] moved vendor js files to common --- {lms => common}/static/js/vendor/Markdown.Converter.js | 0 {lms => common}/static/js/vendor/Markdown.Editor.js | 0 {lms => common}/static/js/vendor/Markdown.Sanitizer.js | 0 {lms => common}/static/js/vendor/customwmd.js | 0 lms/static/coffee/src/customwmd.coffee | 2 +- lms/templates/discussion/index.html | 3 +++ 6 files changed, 4 insertions(+), 1 deletion(-) rename {lms => common}/static/js/vendor/Markdown.Converter.js (100%) rename {lms => common}/static/js/vendor/Markdown.Editor.js (100%) rename {lms => common}/static/js/vendor/Markdown.Sanitizer.js (100%) rename {lms => common}/static/js/vendor/customwmd.js (100%) diff --git a/lms/static/js/vendor/Markdown.Converter.js b/common/static/js/vendor/Markdown.Converter.js similarity index 100% rename from lms/static/js/vendor/Markdown.Converter.js rename to common/static/js/vendor/Markdown.Converter.js diff --git a/lms/static/js/vendor/Markdown.Editor.js b/common/static/js/vendor/Markdown.Editor.js similarity index 100% rename from lms/static/js/vendor/Markdown.Editor.js rename to common/static/js/vendor/Markdown.Editor.js diff --git a/lms/static/js/vendor/Markdown.Sanitizer.js b/common/static/js/vendor/Markdown.Sanitizer.js similarity index 100% rename from lms/static/js/vendor/Markdown.Sanitizer.js rename to common/static/js/vendor/Markdown.Sanitizer.js diff --git a/lms/static/js/vendor/customwmd.js b/common/static/js/vendor/customwmd.js similarity index 100% rename from lms/static/js/vendor/customwmd.js rename to common/static/js/vendor/customwmd.js diff --git a/lms/static/coffee/src/customwmd.coffee b/lms/static/coffee/src/customwmd.coffee index a364b252c7..4c23ffb218 100644 --- a/lms/static/coffee/src/customwmd.coffee +++ b/lms/static/coffee/src/customwmd.coffee @@ -1,5 +1,5 @@ $ -> converter = Markdown.getSanitizingConverter() editor = new Markdown.Editor(converter) - converter.hooks.chain "preConversion", removeMath + #converter.hooks.chain "preConversion", removeMath editor.run() diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index dc491549bc..06331736d1 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -8,6 +8,9 @@ <%block name="js_extra"> + + + <%include file="../course_navigation.html" args="active_page='discussion'" /> From 1a57ac50f43f5531f47035aad07c314d77af9b25 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 11:09:33 -0400 Subject: [PATCH 040/352] custom mathjax setup --- lms/templates/discussion/index.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index 06331736d1..36a5cd1dfb 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -8,6 +8,25 @@ <%block name="js_extra"> + + + + + From eb0a9bc9e888e6ee103ada398399027a4961cb93 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 11:11:30 -0400 Subject: [PATCH 041/352] only run customwmd when where's markdown --- lms/static/coffee/src/customwmd.coffee | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lms/static/coffee/src/customwmd.coffee b/lms/static/coffee/src/customwmd.coffee index 4c23ffb218..94434738c6 100644 --- a/lms/static/coffee/src/customwmd.coffee +++ b/lms/static/coffee/src/customwmd.coffee @@ -1,5 +1,21 @@ $ -> - converter = Markdown.getSanitizingConverter() - editor = new Markdown.Editor(converter) - #converter.hooks.chain "preConversion", removeMath - editor.run() + + if Markdown? + mathRenderer = new MathJaxDelayRenderer() + removeMath = (text) -> text + + replaceMath = (text) -> text + + updateMathJax = -> + console.log "updating" + #mathRenderer.render + # element: $("#wmd-preview") + MathJax.Hub.Queue(["Typeset", MathJax.Hub, "wmd-preview"]) + + + converter = Markdown.getSanitizingConverter() + editor = new Markdown.Editor(converter) + converter.hooks.chain "preConversion", removeMath + converter.hooks.chain "postConversion", replaceMath + editor.hooks.chain "onPreviewRefresh", updateMathJax + editor.run() From b04a6456854cd4425c7753649a32771e6e340eb7 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 11:11:45 -0400 Subject: [PATCH 042/352] make indentation the same --- lms/static/sass/_discussion.scss | 118 ++++++++++++++----------------- 1 file changed, 55 insertions(+), 63 deletions(-) diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index d7ef38e1e4..ae739c9589 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -188,123 +188,115 @@ $discussion_input_width: 60%; } -body -{ - background-color: White; - font-family: sans-serif; -} - .wmd-panel { - margin-left: 25%; - margin-right: 25%; - width: 50%; - min-width: 500px; + margin-left: 25%; + margin-right: 25%; + width: 50%; + min-width: 500px; } .wmd-button-bar { - width: 100%; - background-color: Silver; + width: 100%; + background-color: Silver; } .wmd-input { - height: 300px; - width: 100%; - background-color: Gainsboro; - border: 1px solid DarkGray; + height: 300px; + width: 100%; + background-color: Gainsboro; + border: 1px solid DarkGray; + font-style: normal; } .wmd-preview { - background-color: #c0e0ff; + background-color: #c0e0ff; } .wmd-button-row { - position: relative; - margin-left: 5px; - margin-right: 5px; - margin-bottom: 5px; - margin-top: 10px; - padding: 0px; - height: 20px; + position: relative; + margin-left: 5px; + margin-right: 5px; + margin-bottom: 5px; + margin-top: 10px; + padding: 0px; + height: 20px; } .wmd-spacer { - width: 1px; - height: 20px; - margin-left: 14px; - - position: absolute; - background-color: Silver; - display: inline-block; - list-style: none; + width: 1px; + height: 20px; + margin-left: 14px; + + position: absolute; + background-color: Silver; + display: inline-block; + list-style: none; } .wmd-button { - width: 20px; - height: 20px; - padding-left: 2px; - padding-right: 3px; - position: absolute; - display: inline-block; - list-style: none; - cursor: pointer; + width: 20px; + height: 20px; + padding-left: 2px; + padding-right: 3px; + position: absolute; + display: inline-block; + list-style: none; + cursor: pointer; } .wmd-button > span { - background-image: url('/static/images/wmd-buttons.png'); - background-repeat: no-repeat; - background-position: 0px 0px; - width: 20px; - height: 20px; - display: inline-block; + background-image: url('/static/images/wmd-buttons.png'); + background-repeat: no-repeat; + background-position: 0px 0px; + width: 20px; + height: 20px; + display: inline-block; } .wmd-spacer1 { - left: 50px; + left: 50px; } .wmd-spacer2 { - left: 175px; + left: 175px; } .wmd-spacer3 { - left: 300px; + left: 300px; } - - - .wmd-prompt-background { - background-color: Black; + background-color: Black; } .wmd-prompt-dialog { - border: 1px solid #999999; - background-color: #F5F5F5; + border: 1px solid #999999; + background-color: #F5F5F5; } .wmd-prompt-dialog > div { - font-size: 0.8em; - font-family: arial, helvetica, sans-serif; + font-size: 0.8em; + font-family: arial, helvetica, sans-serif; } .wmd-prompt-dialog > form > input[type="text"] { - border: 1px solid #999999; - color: black; + border: 1px solid #999999; + color: black; } .wmd-prompt-dialog > form > input[type="button"]{ - border: 1px solid #888888; - font-family: trebuchet MS, helvetica, sans-serif; - font-size: 0.8em; - font-weight: bold; + border: 1px solid #888888; + font-family: trebuchet MS, helvetica, sans-serif; + font-size: 0.8em; + font-weight: bold; } From f0ce33c9514b418ed72ddea18e2c36b9ba51b57f Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 15:34:22 -0400 Subject: [PATCH 043/352] make mathjax working with the editor --- common/static/js/vendor/Markdown.Editor.js | 11 +- common/static/js/vendor/split.js | 118 +++++++++++ lms/static/coffee/src/customwmd.coffee | 188 ++++++++++++++++-- .../coffee/src/mathjax_delay_renderer.coffee | 73 +++++++ lms/templates/discussion/index.html | 1 + 5 files changed, 370 insertions(+), 21 deletions(-) create mode 100644 common/static/js/vendor/split.js create mode 100644 lms/static/coffee/src/mathjax_delay_renderer.coffee diff --git a/common/static/js/vendor/Markdown.Editor.js b/common/static/js/vendor/Markdown.Editor.js index 1267058702..9959ade634 100644 --- a/common/static/js/vendor/Markdown.Editor.js +++ b/common/static/js/vendor/Markdown.Editor.js @@ -54,7 +54,7 @@ idPostfix = idPostfix || ""; var hooks = this.hooks = new Markdown.HookCollection(); - hooks.addNoop("onPreviewRefresh"); // called with no arguments after the preview has been refreshed + hooks.addNoop("onPreviewPush"); // called with no arguments after the preview has been refreshed hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text hooks.addFalse("insertImageDialog"); /* called with one parameter: a callback to be called with the URL of the image. If the application creates * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen @@ -72,7 +72,7 @@ panels = new PanelCollection(idPostfix); var commandManager = new CommandManager(hooks); - var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }); + var previewManager = new PreviewManager(markdownConverter, panels, function (text, previewSet) { hooks.onPreviewPush(text, previewSet); }); var undoManager, uiManager; if (!/\?noundo/.test(doc.location.href)) { @@ -769,7 +769,7 @@ this.init(); }; - function PreviewManager(converter, panels, previewRefreshCallback) { + function PreviewManager(converter, panels, previewPushCallback) { var managerObj = this; var timeout; @@ -928,8 +928,7 @@ var emptyTop = position.getTop(panels.input) - getDocScrollTop(); if (panels.preview) { - previewSet(text); - previewRefreshCallback(); + previewPushCallback(text, previewSet); } setPanelScrollTops(); @@ -2157,4 +2156,4 @@ } -})(); \ No newline at end of file +})(); diff --git a/common/static/js/vendor/split.js b/common/static/js/vendor/split.js new file mode 100644 index 0000000000..50a3bbde07 --- /dev/null +++ b/common/static/js/vendor/split.js @@ -0,0 +1,118 @@ +/*! + * Cross-Browser Split 1.1.1 + * Copyright 2007-2012 Steven Levithan + * Available under the MIT License + * ECMAScript compliant, uniform cross-browser split method + */ + +/** + * Splits a string into an array of strings using a regex or string separator. Matches of the + * separator are not included in the result array. However, if `separator` is a regex that contains + * capturing groups, backreferences are spliced into the result each time `separator` is matched. + * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably + * cross-browser. + * @param {String} str String to split. + * @param {RegExp|String} separator Regex or string to use for separating the string. + * @param {Number} [limit] Maximum number of items to include in the result array. + * @returns {Array} Array of substrings. + * @example + * + * // Basic use + * split('a b c d', ' '); + * // -> ['a', 'b', 'c', 'd'] + * + * // With limit + * split('a b c d', ' ', 2); + * // -> ['a', 'b'] + * + * // Backreferences in result array + * split('..word1 word2..', /([a-z]+)(\d+)/i); + * // -> ['..', 'word', '1', ' ', 'word', '2', '..'] + */ + +var _split; // instead of split for a less common name; avoid conflict + +// Avoid running twice; that would break the `nativeSplit` reference +_split = _split || function (undef) { + + var nativeSplit = String.prototype.split, + compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group + self; + + self = function (str, separator, limit) { + // If `separator` is not a regex, use `nativeSplit` + if (Object.prototype.toString.call(separator) !== "[object RegExp]") { + return nativeSplit.call(str, separator, limit); + } + var output = [], + flags = (separator.ignoreCase ? "i" : "") + + (separator.multiline ? "m" : "") + + (separator.extended ? "x" : "") + // Proposed for ES6 + (separator.sticky ? "y" : ""), // Firefox 3+ + lastLastIndex = 0, + // Make `global` and avoid `lastIndex` issues by working with a copy + separator = new RegExp(separator.source, flags + "g"), + separator2, match, lastIndex, lastLength; + str += ""; // Type-convert + if (!compliantExecNpcg) { + // Doesn't need flags gy, but they don't hurt + separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags); + } + /* Values for `limit`, per the spec: + * If undefined: 4294967295 // Math.pow(2, 32) - 1 + * If 0, Infinity, or NaN: 0 + * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; + * If negative number: 4294967296 - Math.floor(Math.abs(limit)) + * If other: Type-convert, then use the above rules + */ + limit = limit === undef ? + -1 >>> 0 : // Math.pow(2, 32) - 1 + limit >>> 0; // ToUint32(limit) + while (match = separator.exec(str)) { + // `separator.lastIndex` is not reliable cross-browser + lastIndex = match.index + match[0].length; + if (lastIndex > lastLastIndex) { + output.push(str.slice(lastLastIndex, match.index)); + // Fix browsers whose `exec` methods don't consistently return `undefined` for + // nonparticipating capturing groups + if (!compliantExecNpcg && match.length > 1) { + match[0].replace(separator2, function () { + for (var i = 1; i < arguments.length - 2; i++) { + if (arguments[i] === undef) { + match[i] = undef; + } + } + }); + } + if (match.length > 1 && match.index < str.length) { + Array.prototype.push.apply(output, match.slice(1)); + } + lastLength = match[0].length; + lastLastIndex = lastIndex; + if (output.length >= limit) { + break; + } + } + if (separator.lastIndex === match.index) { + separator.lastIndex++; // Avoid an infinite loop + } + } + if (lastLastIndex === str.length) { + if (lastLength || !separator.test("")) { + output.push(""); + } + } else { + output.push(str.slice(lastLastIndex)); + } + return output.length > limit ? output.slice(0, limit) : output; + }; + + // For convenience + String.prototype.split = function (separator, limit) { + return self(this, separator, limit); + }; + + return self; + +}(); + diff --git a/lms/static/coffee/src/customwmd.coffee b/lms/static/coffee/src/customwmd.coffee index 94434738c6..6b3a040a35 100644 --- a/lms/static/coffee/src/customwmd.coffee +++ b/lms/static/coffee/src/customwmd.coffee @@ -1,21 +1,179 @@ +# Mostly adapted from math.stackexchange.com: http://cdn.sstatic.net/js/mathjax-editing-new.js + $ -> + HUB = MathJax.Hub + + class MathJaxProcessor + + inlineMark = "$" + math = null + blocks = null + + MATHSPLIT = /// ( + \$\$? # normal inline or display delimiter + | \\(?:begin|end)\{[a-z]*\*?\} # \begin{} \end{} style + | \\[\\{}$] + | [{}] + | (?:\n\s*)+ # only treat as math when there's single new line + | @@\d+@@ # delimiter similar to the one used internally + ) ///i + + CODESPAN = /// + (^|[^\\]) # match beginning or any previous character other than escape delimiter ('/') + (`+) # code span starts + ([^\n]*?[^`\n]) # code content + \2 # code span ends + (?!`) + ///gm + + ###HUB.Queue -> + console.log "initializing" + renderReady = true + HUB.processUpdateTime = 50 + HUB.Config + "HTML-CSS": + EqnChunk: 10 + EqnChunkFactor: 1 + SVG: + EqnChunk: 10 + EqnChunkFactor: 1 + ### + + @processMath: (start, last, preProcess) => + block = blocks.slice(start, last + 1).join("").replace(/&/g, "&") + .replace(//g, ">") + if HUB.Browser.isMSIE + block = block.replace /(%[^\n]*)\n/g, "$1
    \n" + blocks[i] = "" for i in [start+1..last] + blocks[start] = "@@#{math.length}@@" + block = preProcess(block) if preProcess + math.push block + + @removeMath: (text) => + + math = [] + start = end = last = null + braces = 0 + + hasCodeSpans = /`/.test text + if hasCodeSpans + text = text.replace(/~/g, "~T").replace CODESPAN, ($0) -> # replace dollar sign in code span temporarily + $0.replace /\$/g, "~D" + deTilde = (text) -> + text.replace /~([TD])/g, ($0, $1) -> + {T: "~", D: "$"}[$1] + else + deTilde = (text) -> text + + blocks = _split(text.replace(/\r\n?/g, "\n"), MATHSPLIT) + + for current in [1...blocks.length] by 2 + block = blocks[current] + if block.charAt(0) == "@" + blocks[current] = "@@#{math.length}@@" + math.push block + else if start + if block == end + if braces + last = current + else + @processMath(start, current, deTilde) + start = end = last = null + else if block.match /\n.*\n/ + if last + current = last + @processMath(start, current, deTilde) + start = end = last = null + braces = 0 + else if block == "{" + ++braces + else if block == "}" and braces + --braces + else + if block == inlineMark or block == "$$" + start = current + end = block + braces = 0 + else if block.substr(1, 5) == "begin" + start = current + end = "\\end" + block.substr(6) + braces = 0 + + if last + @processMath(start, last, deTilde) + start = end = last = null + + deTilde(blocks.join("")) + + @replaceMath: (text) => + text = text.replace /@@(\d+)@@/g, ($0, $1) => math[$1] + math = null + text + + @updateMathJax: => + HUB.Queue(["Typeset", HUB, "wmd-preview"]) + + + + ### + if not HUB.Cancel? #and 1 == 2 + HUB.cancelTypeset = false + CANCELMESSAGE = "MathJax Canceled" + + HOOKS = [ + { + name: "HTML-CSS Jax Config" + engine: -> window["MathJax"].OutputJax["HTML-CSS"] + }, + { + name: "SVG Jax Config" + engine: -> window["MathJax"].OutputJax["SVG"] + }, + { + name: "TeX Jax Config" + engine: -> window["MathJax"].InputJax.TeX + }, + ] + + for hook in HOOKS + do (hook) -> + HUB.Register.StartupHook hook.name, -> + engine = hook.engine() + engine.Augment + Translate: (script, state) -> + console.log "translating" + if HUB.cancelTypeset or state.cancelled + throw Error(CANCELMESSAGE) + engine.Translate.call(engine, script, state) + + prevProcessError = HUB.processError + HUB.processError = (error, state, type) -> + if error.message != CANCELMESSAGE + return prevProcessError.call(HUB, error, state, type) + else + console.log "handling message" + MathJax.Message.Clear(0, 0) + state.jaxIds = [] + state.jax = {} + state.scripts = [] + state.i = state.j = 0 + state.cancelled = true + return null + + HUB.Cancel = -> + this.cancelTypeset = true + ### + if Markdown? - mathRenderer = new MathJaxDelayRenderer() - removeMath = (text) -> text - - replaceMath = (text) -> text - - updateMathJax = -> - console.log "updating" - #mathRenderer.render - # element: $("#wmd-preview") - MathJax.Hub.Queue(["Typeset", MathJax.Hub, "wmd-preview"]) - - converter = Markdown.getSanitizingConverter() editor = new Markdown.Editor(converter) - converter.hooks.chain "preConversion", removeMath - converter.hooks.chain "postConversion", replaceMath - editor.hooks.chain "onPreviewRefresh", updateMathJax + converter.hooks.chain "preConversion", MathJaxProcessor.removeMath + converter.hooks.chain "postConversion", MathJaxProcessor.replaceMath + delayRenderer = new MathJaxDelayRenderer() + editor.hooks.chain "onPreviewPush", (text, previewSet) -> + delayRenderer.render + text: text + previewSetter: previewSet editor.run() diff --git a/lms/static/coffee/src/mathjax_delay_renderer.coffee b/lms/static/coffee/src/mathjax_delay_renderer.coffee new file mode 100644 index 0000000000..03bb4b3cc9 --- /dev/null +++ b/lms/static/coffee/src/mathjax_delay_renderer.coffee @@ -0,0 +1,73 @@ +getTime = -> + new Date().getTime() + +class @MathJaxDelayRenderer + + maxDelay: 3000 + mathjaxRunning: false + elapsedTime: 0 + mathjaxDelay: 0 + mathjaxTimeout: undefined + bufferId: "mathjax_delay_buffer" + + constructor: (params) -> + params = params || {} + @maxDelay = params["maxDelay"] || @maxDelay + @bufferId = params["buffer"] || @bufferId + if not $("##{@bufferId}").length + $("
    ").attr("id", @bufferId).css("display", "none").appendTo($("body")) + + # render: (params) -> + # params: + # elem: jquery element to be rendered + # text: text to be rendered & put into the element; + # if blank, then just render the current text in the element + # preprocessor: pre-process the text before rendering using MathJax + # if text is blank, it will pre-process the html in the element + # previewSetter: if provided, will pass text back to it instead of + # directly setting the element + + render: (params) -> + + elem = params["element"] + previewSetter = params["previewSetter"] + text = params["text"] + if not text? + text = $(elem).html() + preprocessor = params["preprocessor"] + buffer = $("##{@bufferId}") + + if params["delay"] == false + if preprocessor? + text = preprocessor(text) + $(elem).html(text) + MathJax.Hub.Queue ["Typeset", MathJax.Hub, $(elem).attr("id")] + else + if @mathjaxTimeout + window.clearTimeout(@mathjaxTimeout) + @mathjaxTimeout = undefined + delay = Math.min @elapsedTime + @mathjaxDelay, @maxDelay + + renderer = => + if @mathjaxRunning + return + prevTime = getTime() + if preprocessor? + text = preprocessor(text) + buffer.html(text) + curTime = getTime() + @elapsedTime = curTime - prevTime + if MathJax + prevTime = getTime() + @mathjaxRunning = true + MathJax.Hub.Queue ["Typeset", MathJax.Hub, buffer.attr("id")], => + @mathjaxRunning = false + curTime = getTime() + @mathjaxDelay = curTime - prevTime + if previewSetter + previewSetter($(buffer).html()) + else + $(elem).html($(buffer).html()) + else + @mathjaxDelay = 0 + @mathjaxTimeout = window.setTimeout(renderer, delay) diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index 36a5cd1dfb..db1f2e5153 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -27,6 +27,7 @@ It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of MathJax extension libraries --> + From b37df4d0a5de5aad33154a739d1ae5ab07c49cbd Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 15:35:06 -0400 Subject: [PATCH 044/352] removed the dumb debug line --- lms/djangoapps/django_comment_client/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 603029a7f6..35c2a86a9a 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -35,9 +35,6 @@ def get_discussion_title(request, course, discussion_id): if not _DISCUSSIONINFO: initialize_discussion_info(request, course) title = _DISCUSSIONINFO['by_id'].get(discussion_id, {}).get('title', '(no title)') - if title == '(no title)': - print "title shouldn't be none" - import pdb; pdb.set_trace() return title def initialize_discussion_info(request, course): From dbad9338e18ed8be0ed6d9ba4d3e71bd729353c0 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 16:01:57 -0400 Subject: [PATCH 045/352] removed unnecessary/commented out lines --- lms/static/coffee/src/customwmd.coffee | 67 -------------------------- 1 file changed, 67 deletions(-) diff --git a/lms/static/coffee/src/customwmd.coffee b/lms/static/coffee/src/customwmd.coffee index 6b3a040a35..e893ddf0d1 100644 --- a/lms/static/coffee/src/customwmd.coffee +++ b/lms/static/coffee/src/customwmd.coffee @@ -27,19 +27,6 @@ $ -> (?!`) ///gm - ###HUB.Queue -> - console.log "initializing" - renderReady = true - HUB.processUpdateTime = 50 - HUB.Config - "HTML-CSS": - EqnChunk: 10 - EqnChunkFactor: 1 - SVG: - EqnChunk: 10 - EqnChunkFactor: 1 - ### - @processMath: (start, last, preProcess) => block = blocks.slice(start, last + 1).join("").replace(/&/g, "&") .replace(/ math = null text - @updateMathJax: => - HUB.Queue(["Typeset", HUB, "wmd-preview"]) - - - - ### - if not HUB.Cancel? #and 1 == 2 - HUB.cancelTypeset = false - CANCELMESSAGE = "MathJax Canceled" - - HOOKS = [ - { - name: "HTML-CSS Jax Config" - engine: -> window["MathJax"].OutputJax["HTML-CSS"] - }, - { - name: "SVG Jax Config" - engine: -> window["MathJax"].OutputJax["SVG"] - }, - { - name: "TeX Jax Config" - engine: -> window["MathJax"].InputJax.TeX - }, - ] - - for hook in HOOKS - do (hook) -> - HUB.Register.StartupHook hook.name, -> - engine = hook.engine() - engine.Augment - Translate: (script, state) -> - console.log "translating" - if HUB.cancelTypeset or state.cancelled - throw Error(CANCELMESSAGE) - engine.Translate.call(engine, script, state) - - prevProcessError = HUB.processError - HUB.processError = (error, state, type) -> - if error.message != CANCELMESSAGE - return prevProcessError.call(HUB, error, state, type) - else - console.log "handling message" - MathJax.Message.Clear(0, 0) - state.jaxIds = [] - state.jax = {} - state.scripts = [] - state.i = state.j = 0 - state.cancelled = true - return null - - HUB.Cancel = -> - this.cancelTypeset = true - ### - if Markdown? converter = Markdown.getSanitizingConverter() editor = new Markdown.Editor(converter) From 2e26f84e9bdb3335f3a1d1c34fef1cb4b6c34f75 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 16:20:26 -0400 Subject: [PATCH 046/352] improve for multiple editor on page --- lms/static/coffee/src/customwmd.coffee | 70 +++++++++++-------- lms/static/coffee/src/discussion.coffee | 2 + .../coffee/src/mathjax_delay_renderer.coffee | 13 ++-- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/lms/static/coffee/src/customwmd.coffee b/lms/static/coffee/src/customwmd.coffee index e893ddf0d1..e8012d8f42 100644 --- a/lms/static/coffee/src/customwmd.coffee +++ b/lms/static/coffee/src/customwmd.coffee @@ -6,10 +6,6 @@ $ -> class MathJaxProcessor - inlineMark = "$" - math = null - blocks = null - MATHSPLIT = /// ( \$\$? # normal inline or display delimiter | \\(?:begin|end)\{[a-z]*\*?\} # \begin{} \end{} style @@ -27,20 +23,26 @@ $ -> (?!`) ///gm - @processMath: (start, last, preProcess) => - block = blocks.slice(start, last + 1).join("").replace(/&/g, "&") + constructor: (inlineMark, displayMark) -> + @inlineMark = inlineMark || "$" + @displayMark = displayMark || "$$" + @math = null + @blocks = null + + processMath: (start, last, preProcess) -> + block = @blocks.slice(start, last + 1).join("").replace(/&/g, "&") .replace(//g, ">") if HUB.Browser.isMSIE block = block.replace /(%[^\n]*)\n/g, "$1
    \n" - blocks[i] = "" for i in [start+1..last] - blocks[start] = "@@#{math.length}@@" + @blocks[i] = "" for i in [start+1..last] + @blocks[start] = "@@#{@math.length}@@" block = preProcess(block) if preProcess - math.push block + @math.push block - @removeMath: (text) => + removeMath: (text) -> - math = [] + @math = [] start = end = last = null braces = 0 @@ -54,13 +56,13 @@ $ -> else deTilde = (text) -> text - blocks = _split(text.replace(/\r\n?/g, "\n"), MATHSPLIT) + @blocks = _split(text.replace(/\r\n?/g, "\n"), MATHSPLIT) - for current in [1...blocks.length] by 2 - block = blocks[current] + for current in [1...@blocks.length] by 2 + block = @blocks[current] if block.charAt(0) == "@" - blocks[current] = "@@#{math.length}@@" - math.push block + @blocks[current] = "@@#{@math.length}@@" + @math.push block else if start if block == end if braces @@ -79,7 +81,7 @@ $ -> else if block == "}" and braces --braces else - if block == inlineMark or block == "$$" + if block == @inlineMark or block == @displayMark start = current end = block braces = 0 @@ -92,21 +94,27 @@ $ -> @processMath(start, last, deTilde) start = end = last = null - deTilde(blocks.join("")) + deTilde(@blocks.join("")) - @replaceMath: (text) => - text = text.replace /@@(\d+)@@/g, ($0, $1) => math[$1] - math = null + replaceMath: (text) -> + text = text.replace /@@(\d+)@@/g, ($0, $1) => @math[$1] + @math = null text if Markdown? - converter = Markdown.getSanitizingConverter() - editor = new Markdown.Editor(converter) - converter.hooks.chain "preConversion", MathJaxProcessor.removeMath - converter.hooks.chain "postConversion", MathJaxProcessor.replaceMath - delayRenderer = new MathJaxDelayRenderer() - editor.hooks.chain "onPreviewPush", (text, previewSet) -> - delayRenderer.render - text: text - previewSetter: previewSet - editor.run() + Markdown.getMathCompatibleConverter = -> + converter = Markdown.getSanitizingConverter() + processor = new MathJaxProcessor() + converter.hooks.chain "preConversion", processor.removeMath + converter.hooks.chain "postConversion", processor.replaceMath + converter + + Markdown.makeWmdEditor = (appended_id) -> + converter = Markdown.getMathCompatibleConverter() + editor = new Markdown.Editor(converter, appended_id) + delayRenderer = new MathJaxDelayRenderer() + editor.hooks.chain "onPreviewPush", (text, previewSet) -> + delayRenderer.render + text: text + previewSetter: previewSet + editor.run() diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee index 51420f38cc..50bc95d8e8 100644 --- a/lms/static/coffee/src/discussion.coffee +++ b/lms/static/coffee/src/discussion.coffee @@ -13,6 +13,8 @@ $ -> Discussion.bindDiscussionEvents(discussion) Discussion.initializeDiscussion(discussion) + Markdown.makeWmdEditor() + generateLocal = (elem) -> (selector) -> $(elem).find(selector) diff --git a/lms/static/coffee/src/mathjax_delay_renderer.coffee b/lms/static/coffee/src/mathjax_delay_renderer.coffee index 03bb4b3cc9..b70cb28246 100644 --- a/lms/static/coffee/src/mathjax_delay_renderer.coffee +++ b/lms/static/coffee/src/mathjax_delay_renderer.coffee @@ -13,9 +13,7 @@ class @MathJaxDelayRenderer constructor: (params) -> params = params || {} @maxDelay = params["maxDelay"] || @maxDelay - @bufferId = params["buffer"] || @bufferId - if not $("##{@bufferId}").length - $("
    ").attr("id", @bufferId).css("display", "none").appendTo($("body")) + @$buffer = $("
    ").attr("id", @bufferId).css("display", "none").appendTo($("body")) # render: (params) -> # params: @@ -35,7 +33,6 @@ class @MathJaxDelayRenderer if not text? text = $(elem).html() preprocessor = params["preprocessor"] - buffer = $("##{@bufferId}") if params["delay"] == false if preprocessor? @@ -54,20 +51,20 @@ class @MathJaxDelayRenderer prevTime = getTime() if preprocessor? text = preprocessor(text) - buffer.html(text) + @$buffer.html(text) curTime = getTime() @elapsedTime = curTime - prevTime if MathJax prevTime = getTime() @mathjaxRunning = true - MathJax.Hub.Queue ["Typeset", MathJax.Hub, buffer.attr("id")], => + MathJax.Hub.Queue ["Typeset", MathJax.Hub, @$buffer.attr("id")], => @mathjaxRunning = false curTime = getTime() @mathjaxDelay = curTime - prevTime if previewSetter - previewSetter($(buffer).html()) + previewSetter($(@$buffer).html()) else - $(elem).html($(buffer).html()) + $(elem).html($(@$buffer).html()) else @mathjaxDelay = 0 @mathjaxTimeout = window.setTimeout(renderer, delay) From fc4e21d4c4d5df2b83d22c03d534094cee2a0acc Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 17:35:09 -0400 Subject: [PATCH 047/352] use wmd editor for comments --- lms/static/coffee/src/customwmd.coffee | 13 ++++++++++++- lms/static/coffee/src/discussion.coffee | 20 +++++++++++++++----- lms/static/sass/_discussion.scss | 2 +- lms/templates/discussion/inline.html | 10 +++------- lms/templates/discussion/thread.html | 19 +++++++++++-------- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/lms/static/coffee/src/customwmd.coffee b/lms/static/coffee/src/customwmd.coffee index e8012d8f42..8a3a2d7c97 100644 --- a/lms/static/coffee/src/customwmd.coffee +++ b/lms/static/coffee/src/customwmd.coffee @@ -109,7 +109,18 @@ $ -> converter.hooks.chain "postConversion", processor.replaceMath converter - Markdown.makeWmdEditor = (appended_id) -> + Markdown.makeWmdEditor = (elem, appended_id) -> + $elem = $(elem) + if not $elem.length + console.log "warning: elem for makeWmdEditor doesn't exist" + return + if not $elem.find(".wmd-panel").length + _append = appended_id || "" + $wmdPanel = $("
    ").addClass("wmd-panel") + .append($("
    ").attr("id", "wmd-button-bar#{_append}")) + .append($(" +
    +
    New Post -
    -
    - -
    -
    +
    % for thread in threads: diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html index 5997b1b70c..52eb17c107 100644 --- a/lms/templates/discussion/thread.html +++ b/lms/templates/discussion/thread.html @@ -41,14 +41,17 @@ % for comment in comments:
    - ${render_vote(comment)} -
    -
    - ${comment['body']} -
    - ${render_info(comment)} - ${render_reply()} - ${render_edit()} +
    + ${render_vote(comment)} +
    +
    + ${comment['body']} +
    + ${render_info(comment)} + ${render_reply()} + ${render_edit()} +
    +
    From 03898f4ab7406ee5fc3b3bbc235aa8c746869a7f Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 26 Jul 2012 17:57:37 -0400 Subject: [PATCH 048/352] fixed unmatched div tag --- lms/templates/discussion/thread.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html index 52eb17c107..500ca66585 100644 --- a/lms/templates/discussion/thread.html +++ b/lms/templates/discussion/thread.html @@ -45,12 +45,11 @@ ${render_vote(comment)}
    - ${comment['body']} -
    - ${render_info(comment)} - ${render_reply()} - ${render_edit()} -
    + ${comment['body']} +
    + ${render_info(comment)} + ${render_reply()} + ${render_edit()}
    From aff57bf85552463b4d328fae1340a56c06ef6aa5 Mon Sep 17 00:00:00 2001 From: Brittany Cheng Date: Fri, 27 Jul 2012 14:43:33 -0400 Subject: [PATCH 049/352] mostly font edits --- lms/static/css/course.css | 3450 ++++++++++++++++++++ lms/static/css/course/old/marketing-ie.css | 10 + lms/static/css/course/old/marketing.css | 1017 ++++++ lms/static/css/course/old/print.css | 0 lms/static/sass/_discussion.scss | 2 +- lms/static/sass/_news.scss | 2 +- lms/templates/discussion/index.html | 7 +- lms/templates/discussion/thread.html | 1 + 8 files changed, 4483 insertions(+), 6 deletions(-) create mode 100644 lms/static/css/course.css create mode 100644 lms/static/css/course/old/marketing-ie.css create mode 100644 lms/static/css/course/old/marketing.css create mode 100644 lms/static/css/course/old/print.css diff --git a/lms/static/css/course.css b/lms/static/css/course.css new file mode 100644 index 0000000000..16328fceb9 --- /dev/null +++ b/lms/static/css/course.css @@ -0,0 +1,3450 @@ +@charset "UTF-8"; +/* HTML5 Boilerplate */ +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { + display: block; } + +audio, canvas, video { + display: inline-block; + *display: inline; + *zoom: 1; } + +audio:not([controls]) { + display: none; } + +[hidden] { + display: none; } + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; } + +html, button, input, select, textarea { + font-family: sans-serif; + color: #222; } + +body { + margin: 0; + font-size: 1em; + line-height: 1.4; } + +::-moz-selection { + background: #fe57a1; + color: #fff; + text-shadow: none; } + +::selection { + background: #fe57a1; + color: #fff; + text-shadow: none; } + +a { + color: #00e; } + +a:visited { + color: #551a8b; } + +a:hover { + color: #06e; } + +a:focus { + outline: thin dotted; } + +a:hover, a:active { + outline: 0; } + +abbr[title] { + border-bottom: 1px dotted; } + +b, strong { + font-weight: bold; } + +blockquote { + margin: 1em 40px; } + +dfn { + font-style: italic; } + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 1em 0; + padding: 0; } + +ins { + background: #ff9; + color: #000; + text-decoration: none; } + +mark { + background: #ff0; + color: #000; + font-style: italic; + font-weight: bold; } + +pre, code, kbd, samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; } + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; } + +q { + quotes: none; } + +q:before, q:after { + content: ""; + content: none; } + +small { + font-size: 85%; } + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +ul, ol { + margin: 1em 0; + padding: 0 0 0 40px; } + +dd { + margin: 0 0 0 40px; } + +nav ul, nav ol { + list-style: none; + list-style-image: none; + margin: 0; + padding: 0; } + +img { + border: 0; + -ms-interpolation-mode: bicubic; + vertical-align: middle; } + +svg:not(:root) { + overflow: hidden; } + +figure { + margin: 0; } + +form { + margin: 0; } + +fieldset { + border: 0; + margin: 0; + padding: 0; } + +label { + cursor: pointer; } + +legend { + border: 0; + *margin-left: -7px; + padding: 0; + white-space: normal; } + +button, input, select, textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle; } + +button, input { + line-height: normal; } + +button, input[type="button"], input[type="reset"], input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; + *overflow: visible; } + +button[disabled], input[disabled] { + cursor: default; } + +input[type="checkbox"], input[type="radio"] { + box-sizing: border-box; + padding: 0; + *width: 13px; + *height: 13px; } + +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; } + +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; } + +button::-moz-focus-inner, input::-moz-focus-inner { + border: 0; + padding: 0; } + +textarea { + overflow: auto; + vertical-align: top; + resize: vertical; } + +input:invalid, textarea:invalid { + background-color: #f0dddd; } + +table { + border-collapse: collapse; + border-spacing: 0; } + +td { + vertical-align: top; } + +.chromeframe { + margin: 0.2em 0; + background: #ccc; + color: black; + padding: 0.2em 0; } + +.ir { + display: block; + border: 0; + text-indent: -999em; + overflow: hidden; + background-color: transparent; + background-repeat: no-repeat; + text-align: left; + direction: ltr; + *line-height: 0; } + +.ir br { + display: none; } + +.hidden { + display: none !important; + visibility: hidden; } + +.visuallyhidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } + +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + width: auto; } + +.invisible { + visibility: hidden; } + +.clearfix:before, .topbar:before, nav.sequence-nav:before, div.course-wrapper section.course-content .problem-set:before, div.course-wrapper section.course-content section.problems-wrapper:before, div.course-wrapper section.course-content div#seq_content:before, div.course-wrapper section.course-content ol.vert-mod > li:before, section.course-content nav.sequence-bottom ul:before, section.course-content div.video article.video-wrapper section.video-controls:before, section.course-content div.video article.video-wrapper section.video-controls div.slider:before, section.tool-wrapper:before, section.tool-wrapper div#controlls-container:before, section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper:before, section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper:before, section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders:before, section.problem-set:before, section.problems-wrapper:before, .clearfix:after, .topbar:after, nav.sequence-nav:after, div.course-wrapper section.course-content .problem-set:after, div.course-wrapper section.course-content section.problems-wrapper:after, div.course-wrapper section.course-content div#seq_content:after, div.course-wrapper section.course-content ol.vert-mod > li:after, section.course-content nav.sequence-bottom ul:after, section.course-content div.video article.video-wrapper section.video-controls:after, section.course-content div.video article.video-wrapper section.video-controls div.slider:after, section.tool-wrapper:after, section.tool-wrapper div#controlls-container:after, section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper:after, section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper:after, section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders:after, section.problem-set:after, section.problems-wrapper:after { + content: ""; + display: table; } + +.clearfix:after, .topbar:after, nav.sequence-nav:after, div.course-wrapper section.course-content .problem-set:after, div.course-wrapper section.course-content section.problems-wrapper:after, div.course-wrapper section.course-content div#seq_content:after, div.course-wrapper section.course-content ol.vert-mod > li:after, section.course-content nav.sequence-bottom ul:after, section.course-content div.video article.video-wrapper section.video-controls:after, section.course-content div.video article.video-wrapper section.video-controls div.slider:after, section.tool-wrapper:after, section.tool-wrapper div#controlls-container:after, section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper:after, section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper:after, section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders:after, section.problem-set:after, section.problems-wrapper:after { + clear: both; } + +.clearfix, .topbar, nav.sequence-nav, div.course-wrapper section.course-content .problem-set, div.course-wrapper section.course-content section.problems-wrapper, div.course-wrapper section.course-content div#seq_content, div.course-wrapper section.course-content ol.vert-mod > li, section.course-content nav.sequence-bottom ul, section.course-content div.video article.video-wrapper section.video-controls, section.course-content div.video article.video-wrapper section.video-controls div.slider, section.tool-wrapper, section.tool-wrapper div#controlls-container, section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper, section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper, section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders, section.problem-set, section.problems-wrapper { + *zoom: 1; } + +@media print { + * { + background: transparent !important; + color: black !important; + box-shadow: none !important; + text-shadow: none !important; + filter: none !important; + -ms-filter: none !important; } + + a, a:visited { + text-decoration: underline; } + + a[href]:after { + content: " (" attr(href) ")"; } + + abbr[title]:after { + content: " (" attr(title) ")"; } + + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { + content: ""; } + + pre, blockquote { + border: 1px solid #999; + page-break-inside: avoid; } + + thead { + display: table-header-group; } + + tr, img { + page-break-inside: avoid; } + + img { + max-width: 100% !important; } + + @page { + margin: 0.5cm; } + + p, h2, h3 { + orphans: 3; + widows: 3; } + + h2, h3 { + page-break-after: avoid; } } +/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 25, 2012 05:06:34 PM America/New_York */ +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-Light-webfont.eot"); + src: url("../fonts/OpenSans-Light-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Light-webfont.woff") format("woff"), url("../fonts/OpenSans-Light-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Light-webfont.svg#OpenSansLight") format("svg"); + font-weight: 300; + font-style: normal; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-LightItalic-webfont.eot"); + src: url("../fonts/OpenSans-LightItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-LightItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-LightItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-LightItalic-webfont.svg#OpenSansLightItalic") format("svg"); + font-weight: 300; + font-style: italic; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-Regular-webfont.eot"); + src: url("../fonts/OpenSans-Regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Regular-webfont.woff") format("woff"), url("../fonts/OpenSans-Regular-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular") format("svg"); + font-weight: 400; + font-style: normal; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-Italic-webfont.eot"); + src: url("../fonts/OpenSans-Italic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Italic-webfont.woff") format("woff"), url("../fonts/OpenSans-Italic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic") format("svg"); + font-weight: 400; + font-style: italic; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-Bold-webfont.eot"); + src: url("../fonts/OpenSans-Bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Bold-webfont.woff") format("woff"), url("../fonts/OpenSans-Bold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Bold-webfont.svg#OpenSansBold") format("svg"); + font-weight: 700; + font-style: normal; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-BoldItalic-webfont.eot"); + src: url("../fonts/OpenSans-BoldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-BoldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-BoldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic") format("svg"); + font-weight: 700; + font-style: italic; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-ExtraBold-webfont.eot"); + src: url("../fonts/OpenSans-ExtraBold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-ExtraBold-webfont.woff") format("woff"), url("../fonts/OpenSans-ExtraBold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-ExtraBold-webfont.svg#OpenSansExtrabold") format("svg"); + font-weight: 800; + font-style: normal; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-ExtraBoldItalic-webfont.eot"); + src: url("../fonts/OpenSans-ExtraBoldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.svg#OpenSansExtraboldItalic") format("svg"); + font-weight: 800; + font-style: italic; } + +html, body { + background: #fafafa; + font-family: "Open Sans", Verdana, Geneva, sans-serif; + font-size: 1em; + line-height: 1em; } + +h1, h2, h3, h4, h5, h6 { + color: #3c3c3c; + font: normal 1.2em/1.2em Georgia, Cambria, "Times New Roman", Times, serif; + margin: 0px; } + +h1 { + color: #3c3c3c; + font: normal 2em/1.4em "Open Sans", Verdana, Geneva, sans-serif; + letter-spacing: 1px; + margin-bottom: 30px; + text-align: center; } + +h2 { + color: #a0a0a0; + font: normal 1.2em/1.2em Georgia, Cambria, "Times New Roman", Times, serif; + letter-spacing: 1px; + margin-bottom: 15px; + text-transform: uppercase; + -webkit-font-smoothing: antialiased; } + +p + h2, ul + h2, ol + h2 { + margin-top: 40px; } + +p { + color: #3c3c3c; + font: normal 1em/1.6em Georgia, Cambria, "Times New Roman", Times, serif; + margin: 0px; } + +span { + font: normal 1em/1.6em "Open Sans", Verdana, Geneva, sans-serif; } + +p + p, ul + p, ol + p { + margin-top: 20px; } + +p a:link, p a:visited { + color: #1d9dd9; + font: normal 1em/1em Georgia, Cambria, "Times New Roman", Times, serif; + text-decoration: none; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + p a:link:hover, p a:visited:hover { + color: #1d9dd9; + text-decoration: underline; } + +a:link, a:visited { + color: #1d9dd9; + font: normal 1em/1em "Open Sans", Verdana, Geneva, sans-serif; + text-decoration: none; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + a:link:hover, a:visited:hover { + text-decoration: underline; } + +.content-wrapper { + background: white; + margin: 0 auto 0; + width: 100%; } + +.container { + zoom: 1; + margin: 0 auto 0; + padding: 0px 30px; + max-width: 1180px; + min-width: 760px; } + .container:before, .container:after { + content: ""; + display: table; } + .container:after { + clear: both; } + +span.edx { + text-transform: none; + font: inherit; } + +.static-container { + zoom: 1; + margin: 0 auto 0; + max-width: 1200px; + padding: 60px 0px 120px; + width: 100%; } + .static-container:before, .static-container:after { + content: ""; + display: table; } + .static-container:after { + clear: both; } + .static-container .inner-wrapper { + margin: 0 auto 0; + width: 83.051%; } + .static-container ol, .static-container ul { + list-style: disc; } + .static-container ol li, .static-container ul li { + color: #3c3c3c; + font: normal 1em/1.4em Georgia, Cambria, "Times New Roman", Times, serif; + margin: 0px; } + .static-container h1 { + margin-bottom: 30px; } + .static-container h1 + hr { + margin-bottom: 60px; } + .static-container p + h2, .static-container ul + h2, .static-container ol + h2 { + margin-top: 40px; } + .static-container ul + p, .static-container ol + p { + margin-top: 20px; } + +.faded-hr-divider, .horizontal-divider { + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + height: 1px; + width: 100%; } + +.faded-hr-divider-medium { + background-image: compact(linear, compact(180deg, rgba(240, 240, 240, 0) 0%, #f0f0f0 50%, rgba(240, 240, 240, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(240, 240, 240, 0) 0%, #f0f0f0 50%, rgba(240, 240, 240, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(240, 240, 240, 0) 0%, #f0f0f0 50%, rgba(240, 240, 240, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(240, 240, 240, 0) 0%, #f0f0f0 50%, rgba(240, 240, 240, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(240, 240, 240, 0) 0%, #f0f0f0 50%, rgba(240, 240, 240, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + height: 1px; + width: 100%; } + +.faded-hr-divider-light, .horizontal-divider::after { + background-image: compact(linear, compact(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + height: 1px; + width: 100%; } + +.faded-vertical-divider, .vertical-divider { + background-image: compact(linear, compact(90deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(200, 200, 200, 0) 0%, #c8c8c8 50%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + height: 100%; + width: 1px; } + +.faded-vertical-divider-light, .vertical-divider::after { + background-image: compact(linear, compact(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0), false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + height: 100%; + width: 1px; } + +.vertical-divider { + position: relative; } + .vertical-divider::after { + content: ""; + display: block; + position: absolute; + left: 1px; } + +.horizontal-divider { + border: none; + position: relative; } + .horizontal-divider::after { + content: ""; + display: block; + position: absolute; + top: 1px; } + +.fade-right-hr-divider { + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, rgba(200, 200, 200, 0) 0%, #c8c8c8, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + border: none; } + +.fade-left-hr-divider { + background-image: compact(linear, compact(180deg, #c8c8c8 0%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, #c8c8c8 0%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, #c8c8c8 0%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, #c8c8c8 0%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(180deg, #c8c8c8 0%, rgba(200, 200, 200, 0), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + border: none; } + +.error-message-colors { + background: #fd5757; + border: 1px solid #ca1111; + color: #8f0e0e; } + +.success-message-colors { + background: #139f3a; + border: 1px solid #064112; + color: white; } + +.animation-home-header-pop-up { + -webkit-animation: compact(home-header-pop-up 1.15s ease-in-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(home-header-pop-up 1.15s ease-in-out, false, false, false, false, false, false, false, false); + animation: compact(home-header-pop-up 1.15s ease-in-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(1s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(1s, false, false, false, false, false, false, false, false); + animation-delay: compact(1s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes home-header-pop-up { + 0% { + opacity: 0; + top: 300px; } + + 45% { + opacity: 1; } + + 65% { + top: -40px; } + + 85% { + top: 10px; } + + 100% { + top: 0px; } } + +@-moz-keyframes home-header-pop-up { + 0% { + opacity: 0; + top: 300px; } + + 45% { + opacity: 1; } + + 65% { + top: -40px; } + + 85% { + top: 10px; } + + 100% { + top: 0px; } } + +@keyframes home-header-pop-up { + 0% { + opacity: 0; + top: 300px; } + + 45% { + opacity: 1; } + + 65% { + top: -40px; } + + 85% { + top: 10px; } + + 100% { + top: 0px; } } + +.animation-title-appear { + -webkit-animation: compact(title-appear 4.65s ease-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(title-appear 4.65s ease-out, false, false, false, false, false, false, false, false); + animation: compact(title-appear 4.65s ease-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(1s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(1s, false, false, false, false, false, false, false, false); + animation-delay: compact(1s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes title-appear { + 0% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.9); + -moz-transform: scale(0.9); + -ms-transform: scale(0.9); + -o-transform: scale(0.9); + transform: scale(0.9); } + + 20% { + opacity: 1; } + + 27% { + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 90% { + opacity: 1; + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 100% { + top: 0px; } } + +@-moz-keyframes title-appear { + 0% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.9); + -moz-transform: scale(0.9); + -ms-transform: scale(0.9); + -o-transform: scale(0.9); + transform: scale(0.9); } + + 20% { + opacity: 1; } + + 27% { + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 90% { + opacity: 1; + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 100% { + top: 0px; } } + +@keyframes title-appear { + 0% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.9); + -moz-transform: scale(0.9); + -ms-transform: scale(0.9); + -o-transform: scale(0.9); + transform: scale(0.9); } + + 20% { + opacity: 1; } + + 27% { + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 90% { + opacity: 1; + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 100% { + top: 0px; } } + +.animation-home-appear { + -webkit-animation: compact(home-appear 4.25s ease-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(home-appear 4.25s ease-out, false, false, false, false, false, false, false, false); + animation: compact(home-appear 4.25s ease-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(1s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(1s, false, false, false, false, false, false, false, false); + animation-delay: compact(1s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes home-appear { + 0% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.9); + -moz-transform: scale(0.9); + -ms-transform: scale(0.9); + -o-transform: scale(0.9); + transform: scale(0.9); } + + 20% { + opacity: 1; } + + 30% { + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 80% { + opacity: 1; + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 100% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -ms-transform: scale(0.7); + -o-transform: scale(0.7); + transform: scale(0.7); } } + +@-moz-keyframes home-appear { + 0% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.9); + -moz-transform: scale(0.9); + -ms-transform: scale(0.9); + -o-transform: scale(0.9); + transform: scale(0.9); } + + 20% { + opacity: 1; } + + 30% { + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 80% { + opacity: 1; + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 100% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -ms-transform: scale(0.7); + -o-transform: scale(0.7); + transform: scale(0.7); } } + +@keyframes home-appear { + 0% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.9); + -moz-transform: scale(0.9); + -ms-transform: scale(0.9); + -o-transform: scale(0.9); + transform: scale(0.9); } + + 20% { + opacity: 1; } + + 30% { + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 80% { + opacity: 1; + top: 40px; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + + 100% { + opacity: 0; + top: 60px; + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -ms-transform: scale(0.7); + -o-transform: scale(0.7); + transform: scale(0.7); } } + +.animation-edx-appear { + -webkit-animation: compact(edx-appear 1.25s ease-in, false, false, false, false, false, false, false, false); + -moz-animation: compact(edx-appear 1.25s ease-in, false, false, false, false, false, false, false, false); + animation: compact(edx-appear 1.25s ease-in, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(2.15s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(2.15s, false, false, false, false, false, false, false, false); + animation-delay: compact(2.15s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes edx-appear { + 0% { + opacity: 0; } + + 100% { + opacity: 1; } } + +@-moz-keyframes edx-appear { + 0% { + opacity: 0; } + + 100% { + opacity: 1; } } + +@keyframes edx-appear { + 0% { + opacity: 0; } + + 100% { + opacity: 1; } } + +.animation-mit-slide { + -webkit-animation: compact(mit-slide 1.15s ease-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(mit-slide 1.15s ease-out, false, false, false, false, false, false, false, false); + animation: compact(mit-slide 1.15s ease-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + animation-delay: compact(2s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes mit-slide { + 0% { + left: 80px; } + + 100% { + left: 0px; } } + +@-moz-keyframes mit-slide { + 0% { + left: 80px; } + + 100% { + left: 0px; } } + +@keyframes mit-slide { + 0% { + left: 80px; } + + 100% { + left: 0px; } } + +.animation-harvard-slide { + -webkit-animation: compact(harvard-slide 1.15s ease-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(harvard-slide 1.15s ease-out, false, false, false, false, false, false, false, false); + animation: compact(harvard-slide 1.15s ease-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + animation-delay: compact(2s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes harvard-slide { + 0% { + right: 80px; } + + 100% { + right: 0px; } } + +@-moz-keyframes harvard-slide { + 0% { + right: 80px; } + + 100% { + right: 0px; } } + +@keyframes harvard-slide { + 0% { + right: 80px; } + + 100% { + right: 0px; } } + +.animation-divider-left-slide { + -webkit-animation: compact(divider-left-slide 1.1s ease-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(divider-left-slide 1.1s ease-out, false, false, false, false, false, false, false, false); + animation: compact(divider-left-slide 1.1s ease-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + animation-delay: compact(2s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes divider-left-slide { + 0% { + left: 340px; } + + 100% { + left: 200px; } } + +@-moz-keyframes divider-left-slide { + 0% { + left: 340px; } + + 100% { + left: 200px; } } + +@keyframes divider-left-slide { + 0% { + left: 340px; } + + 100% { + left: 200px; } } + +.animation-divider-right-slide { + -webkit-animation: compact(divider-right-slide 1.1s ease-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(divider-right-slide 1.1s ease-out, false, false, false, false, false, false, false, false); + animation: compact(divider-right-slide 1.1s ease-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(2s, false, false, false, false, false, false, false, false); + animation-delay: compact(2s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes divider-right-slide { + 0% { + left: 340px; } + + 100% { + left: 480px; } } + +@-moz-keyframes divider-right-slide { + 0% { + left: 340px; } + + 100% { + left: 480px; } } + +@keyframes divider-right-slide { + 0% { + left: 340px; } + + 100% { + left: 480px; } } + +.animation-video-appear { + -webkit-animation: compact(video-appear 1.25s ease-out, false, false, false, false, false, false, false, false); + -moz-animation: compact(video-appear 1.25s ease-out, false, false, false, false, false, false, false, false); + animation: compact(video-appear 1.25s ease-out, false, false, false, false, false, false, false, false); + -webkit-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -moz-animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + animation-fill-mode: compact(both, false, false, false, false, false, false, false, false); + -webkit-animation-delay: compact(4.4s, false, false, false, false, false, false, false, false); + -moz-animation-delay: compact(4.4s, false, false, false, false, false, false, false, false); + animation-delay: compact(4.4s, false, false, false, false, false, false, false, false); } + +@-webkit-keyframes video-appear { + 0% { + bottom: -270px; + opacity: 0.9; } + + 80% { + opacity: 1; } + + 100% { + bottom: 0px; } } + +@-moz-keyframes video-appear { + 0% { + bottom: -270px; + opacity: 0.9; } + + 80% { + opacity: 1; } + + 100% { + bottom: 0px; } } + +@keyframes video-appear { + 0% { + bottom: -270px; + opacity: 0.9; } + + 80% { + opacity: 1; } + + 100% { + bottom: 0px; } } + +nav.course-material { + background: #d2d2d2; + zoom: 1; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-box-shadow: compact(inset 0 1px 5px 0 rgba(0, 0, 0, 0.05), false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 5px 0 rgba(0, 0, 0, 0.05), false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 5px 0 rgba(0, 0, 0, 0.05), false, false, false, false, false, false, false, false); + border-bottom: 1px solid #bebebe; + margin: 0px auto 0px; + padding: 0px; + width: 100%; } + nav.course-material:before, nav.course-material:after { + content: ""; + display: table; } + nav.course-material:after { + clear: both; } + nav.course-material .inner-wrapper { + margin: 0 auto; + max-width: 1200px; + width: 100%; } + nav.course-material ol.course-tabs { + -webkit-border-top-left-radius: 4px; + -moz-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + -ms-border-top-left-radius: 4px; + -o-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -moz-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + -ms-border-top-right-radius: 4px; + -o-border-top-right-radius: 4px; + border-top-right-radius: 4px; + zoom: 1; + padding: 10px 0 0 0; } + nav.course-material ol.course-tabs:before, nav.course-material ol.course-tabs:after { + content: ""; + display: table; } + nav.course-material ol.course-tabs:after { + clear: both; } + nav.course-material ol.course-tabs li { + float: left; + list-style: none; } + nav.course-material ol.course-tabs li a { + color: #a0a0a0; + display: block; + text-align: center; + padding: 5px 13px; + text-decoration: none; + text-shadow: 0 1px rgba(255, 255, 255, 0.4); } + nav.course-material ol.course-tabs li a:hover { + color: #3c3c3c; } + nav.course-material ol.course-tabs li a.active, nav.course-material nav.sequence-nav ol.course-tabs li a.seq_video_active, nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video_active, nav.course-material nav.sequence-nav ol.course-tabs li a.seq_other_active, nav.sequence-nav nav.course-material ol.course-tabs li a.seq_other_active, nav.course-material nav.sequence-nav ol.course-tabs li a.seq_vertical_active, nav.sequence-nav nav.course-material ol.course-tabs li a.seq_vertical_active, nav.course-material nav.sequence-nav ol.course-tabs li a.seq_problem_active, nav.sequence-nav nav.course-material ol.course-tabs li a.seq_problem_active { + background: white; + border: 1px solid #c8c8c8; + border-bottom: 0px; + -webkit-border-top-left-radius: 4px; + -moz-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + -ms-border-top-left-radius: 4px; + -o-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -moz-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + -ms-border-top-right-radius: 4px; + -o-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -webkit-box-shadow: compact(0 2px 0 0 white, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 2px 0 0 white, false, false, false, false, false, false, false, false); + box-shadow: compact(0 2px 0 0 white, false, false, false, false, false, false, false, false); + color: #3c3c3c; } + +.course-content { + margin-top: 30px; } + .course-content .courseware { + background: #f0f0f0; + height: 600px; } + +.clearfix:after, .topbar:after, nav.sequence-nav:after, div.course-wrapper section.course-content .problem-set:after, div.course-wrapper section.course-content section.problems-wrapper:after, div.course-wrapper section.course-content div#seq_content:after, div.course-wrapper section.course-content ol.vert-mod > li:after, section.course-content nav.sequence-bottom ul:after, section.course-content div.video article.video-wrapper section.video-controls:after, section.course-content div.video article.video-wrapper section.video-controls div.slider:after, section.tool-wrapper:after, section.tool-wrapper div#controlls-container:after, section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper:after, section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper:after, section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders:after, section.problem-set:after, section.problems-wrapper:after { + clear: both; + content: "."; + display: block; + height: 0; + visibility: hidden; } + +.wrapper { + margin: 0 auto; + max-width: 1400px; + min-width: 810px; + text-align: left; + width: 100%; } + .wrapper div.table-wrapper, .wrapper div.course-wrapper { + display: table; + width: 100%; + overflow: hidden; } + @media screen and (min-width: 1400px) { + .wrapper div.table-wrapper, .wrapper div.course-wrapper { + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + border-radius: 4px; } } + +h1.top-header, div.course-wrapper section.course-content ol.vert-mod > li header { + background: #f3f3f3; + border-bottom: 1px solid #e3e3e3; + margin: -lh() -lh() lh(); + padding: lh(); } + +.button { + border: 1px solid #6f6f6f; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: compact(inset 0 1px 0 #a2a2a2, 0 0 3px #cccccc, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #a2a2a2, 0 0 3px #cccccc, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #a2a2a2, 0 0 3px #cccccc, false, false, false, false, false, false, false); + color: #fff; + cursor: pointer; + font: bold 14px "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + background-color: #959595; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#959595, #7b7b7b, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#959595, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#959595, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#959595, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#959595, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#959595, #7b7b7b, false, false, false, false, false, false, false, false)); + padding: 4px 8px; + text-decoration: none; + text-shadow: none; + -webkit-font-smoothing: antialiased; } + .button:hover, .button:focus { + border: 1px solid #555555; + -webkit-box-shadow: compact(inset 0 1px 0 #bbbbbb, 0 0 3px #cccccc, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #bbbbbb, 0 0 3px #cccccc, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #bbbbbb, 0 0 3px #cccccc, false, false, false, false, false, false, false); + background-color: #a2a2a2; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#a2a2a2, #7b7b7b, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#a2a2a2, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#a2a2a2, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#a2a2a2, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#a2a2a2, #7b7b7b, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#a2a2a2, #7b7b7b, false, false, false, false, false, false, false, false)); } + +.light-button, a.light-button { + border: 1px solid #ccc; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: compact(inset 0 1px 0 white, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 white, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 white, false, false, false, false, false, false, false, false); + color: #666; + cursor: pointer; + font: normal 14px "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + background-color: white; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(white, #eeeeee, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(white, #eeeeee, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(white, #eeeeee, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(white, #eeeeee, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(white, #eeeeee, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(white, #eeeeee, false, false, false, false, false, false, false, false)); + padding: 4px 8px; + text-decoration: none; + -webkit-font-smoothing: antialiased; } + .light-button:hover, .light-button:focus, a.light-button:hover, a.light-button:focus { + border: 1px solid #ccc; + background-color: white; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(white, #e6e6e6, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(white, #e6e6e6, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(white, #e6e6e6, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(white, #e6e6e6, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(white, #e6e6e6, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(white, #e6e6e6, false, false, false, false, false, false, false, false)); + text-decoration: none; } + +.action-link a { + color: #993333; } + .action-link a:hover { + color: #4d1919; + text-decoration: none; } + +.content, div.course-wrapper section.course-content { + -webkit-box-shadow: compact(inset 0 0 2px 3px #f3f3f3, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 2px 3px #f3f3f3, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 2px 3px #f3f3f3, false, false, false, false, false, false, false, false); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; + padding: lh(); + vertical-align: top; + width: 76.518%; + overflow: hidden; } + @media print { + .content, div.course-wrapper section.course-content { + -webkit-box-shadow: compact(none, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(none, false, false, false, false, false, false, false, false); + box-shadow: compact(none, false, false, false, false, false, false, false, false); } } + +.sidebar, section.course-index { + background: #e3e3e3; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + -ms-border-radius: 4px 0 0 4px; + -o-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; + border-right: 1px solid #d3d3d3; + -webkit-box-shadow: compact(inset 0 0 0 1px #f6f6f6, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 0 1px #f6f6f6, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 0 1px #f6f6f6, false, false, false, false, false, false, false, false); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; + font-family: "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + position: relative; + text-shadow: 0 1px 0 #f1f1f1; + vertical-align: top; + width: 23.482%; } + .sidebar h1, section.course-index h1, .sidebar h2, section.course-index h2 { + font-size: 18px; + font-weight: bold; + letter-spacing: 0; + text-transform: none; } + .sidebar a, section.course-index a { + border: none; + font-style: normal; } + .sidebar .bottom-border, section.course-index .bottom-border { + border-bottom: 1px solid #d3d3d3; + -webkit-box-shadow: compact(0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); } + @media print { + .sidebar, section.course-index { + display: none; } } + .sidebar h3, section.course-index h3 { + background: none; + border: none; + color: #000; + font-weight: normal; + margin: 0; + overflow: hidden; } + .sidebar h3 a, section.course-index h3 a { + color: #4d4d4d; + display: block; + font-size: 14px; + padding: 7px 7px 7px 30px; + text-decoration: none; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + .sidebar h3 span.ui-icon, section.course-index h3 span.ui-icon { + background-image: url(../images/ui-icons_454545_256x240.png); } + .sidebar h3.active, section.course-index h3.active, .sidebar section.course-index div#accordion h3.ui-accordion-header.ui-state-active, section.course-index div#accordion .sidebar h3.ui-accordion-header.ui-state-active, section.course-index div#accordion h3.ui-accordion-header.ui-state-active { + background: none; + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + border-bottom: 1px solid #d3d3d3; + -webkit-box-shadow: compact(inset 0 1px 0 0 #eeeeee, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #eeeeee, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #eeeeee, false, false, false, false, false, false, false, false); + color: #000; + font-weight: bold; } + .sidebar h3.active a, section.course-index h3.active a, .sidebar section.course-index div#accordion h3.ui-accordion-header.ui-state-active a, section.course-index div#accordion .sidebar h3.ui-accordion-header.ui-state-active a, section.course-index div#accordion h3.ui-accordion-header.ui-state-active a { + color: #000; } + .sidebar header#open_close_accordion, section.course-index header#open_close_accordion { + border-bottom: 1px solid #d3d3d3; + -webkit-box-shadow: compact(0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + padding: lh(0.5) lh(); + position: relative; } + .sidebar header#open_close_accordion h2, section.course-index header#open_close_accordion h2 { + margin: 0; + padding-right: 20px; } + .sidebar header#open_close_accordion a, section.course-index header#open_close_accordion a { + background: #eeeeee url("../images/slide-left-icon.png") center center no-repeat; + border: 1px solid #D3D3D3; + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + -ms-border-radius: 3px 0 0 3px; + -o-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; + height: 16px; + padding: 8px; + position: absolute; + right: -1px; + text-indent: -9999px; + top: 6px; + width: 16px; } + .sidebar header#open_close_accordion a:hover, section.course-index header#open_close_accordion a:hover { + background-color: white; } + .sidebar a.button, section.course-index a.button { + text-decoration: none; } + +.topbar, nav.sequence-nav { + background: #f6efd4; + border-bottom: 1px solid #eddfaa; + border-top: 1px solid #fff; + font-size: 12px; + line-height: 46px; + text-shadow: 0 1px 0 #fff; } + @media print { + .topbar, nav.sequence-nav { + display: none; } } + .topbar a, nav.sequence-nav a { + line-height: 46px; + border-bottom: 0; + color: #292309; } + .topbar a:hover, nav.sequence-nav a:hover { + color: #7e691a; + text-decoration: none; } + .topbar a.block-link, nav.sequence-nav a.block-link, .topbar nav.sequence-nav ol a, nav.sequence-nav ol .topbar a, nav.sequence-nav ol a { + border-left: 1px solid #e4d080; + -webkit-box-shadow: compact(inset 1px 0 0 #faf7e9, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 1px 0 0 #faf7e9, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 1px 0 0 #faf7e9, false, false, false, false, false, false, false, false); + display: block; + text-transform: uppercase; } + .topbar a.block-link:hover, nav.sequence-nav a.block-link:hover, .topbar nav.sequence-nav ol a:hover, nav.sequence-nav ol .topbar a:hover, nav.sequence-nav ol a:hover { + background: none; } + +.tran, section.course-index { + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + +p.ie-warning { + background: yellow; + display: block !important; + line-height: 1.3em; + margin-bottom: 0; + padding: lh(); + text-align: left; } + +html { + height: 100%; + max-height: 100%; } + +body.courseware { + height: 100%; + max-height: 100%; } + body.courseware .container { + margin-bottom: 40px; + margin-top: 20px; } + body.courseware footer.fixed-bottom { + Position: static; } + +div.course-wrapper ul, div.course-wrapper ol { + list-style: none; } +div.course-wrapper section.course-content { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + -ms-border-radius: 0 4px 4px 0; + -o-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; } + div.course-wrapper section.course-content h1 { + margin: 0 0 22.652px; } + div.course-wrapper section.course-content p { + margin-bottom: 22.652px; } + div.course-wrapper section.course-content p:empty { + display: none; + margin-bottom: 0; } + div.course-wrapper section.course-content ul li { + margin-bottom: 11.326px; } + div.course-wrapper section.course-content .problem-set, div.course-wrapper section.course-content section.problems-wrapper, div.course-wrapper section.course-content div#seq_content, div.course-wrapper section.course-content ol.vert-mod > li, div.course-wrapper section.course-content section.problems-wrapper, div.course-wrapper section.course-content div#seq_content { + position: relative; } + div.course-wrapper section.course-content .problem-set h2, div.course-wrapper section.course-content section.problems-wrapper h2, div.course-wrapper section.course-content div#seq_content h2, div.course-wrapper section.course-content ol.vert-mod > li h2, div.course-wrapper section.course-content section.problems-wrapper h2, div.course-wrapper section.course-content div#seq_content h2 { + margin-top: 0; + margin-bottom: 15px; + width: 20.109%; + padding-right: 2.717%; + border-right: 1px dashed #ddd; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; + vertical-align: top; } + div.course-wrapper section.course-content .problem-set h2.problem-header section.staff, div.course-wrapper section.course-content section.problems-wrapper h2.problem-header section.staff, div.course-wrapper section.course-content div#seq_content h2.problem-header section.staff, div.course-wrapper section.course-content ol.vert-mod > li h2.problem-header section.staff, div.course-wrapper section.course-content section.problems-wrapper h2.problem-header section.staff, div.course-wrapper section.course-content div#seq_content h2.problem-header section.staff { + margin-top: 30px; + font-size: 80%; } + @media screen and (max-width:1120px) { + div.course-wrapper section.course-content .problem-set h2, div.course-wrapper section.course-content section.problems-wrapper h2, div.course-wrapper section.course-content div#seq_content h2, div.course-wrapper section.course-content ol.vert-mod > li h2, div.course-wrapper section.course-content section.problems-wrapper h2, div.course-wrapper section.course-content div#seq_content h2 { + display: block; + width: auto; + border-right: 0; } } + @media print { + div.course-wrapper section.course-content .problem-set h2, div.course-wrapper section.course-content section.problems-wrapper h2, div.course-wrapper section.course-content div#seq_content h2, div.course-wrapper section.course-content ol.vert-mod > li h2, div.course-wrapper section.course-content section.problems-wrapper h2, div.course-wrapper section.course-content div#seq_content h2 { + display: block; + width: auto; + border-right: 0; } } + div.course-wrapper section.course-content .problem-set section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, div.course-wrapper section.course-content div#seq_content section.problem, div.course-wrapper section.course-content ol.vert-mod > li section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, div.course-wrapper section.course-content div#seq_content section.problem { + display: table-cell; + width: 77.174%; + padding-left: 2.717%; } + @media screen and (max-width:1120px) { + div.course-wrapper section.course-content .problem-set section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, div.course-wrapper section.course-content div#seq_content section.problem, div.course-wrapper section.course-content ol.vert-mod > li section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, div.course-wrapper section.course-content div#seq_content section.problem { + display: block; + width: auto; + padding: 0; } } + @media print { + div.course-wrapper section.course-content .problem-set section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, div.course-wrapper section.course-content div#seq_content section.problem, div.course-wrapper section.course-content ol.vert-mod > li section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, div.course-wrapper section.course-content div#seq_content section.problem { + display: block; + width: auto; + padding: 0; } + div.course-wrapper section.course-content .problem-set section.problem canvas, div.course-wrapper section.course-content section.problems-wrapper section.problem canvas, div.course-wrapper section.course-content div#seq_content section.problem canvas, div.course-wrapper section.course-content ol.vert-mod > li section.problem canvas, div.course-wrapper section.course-content section.problems-wrapper section.problem canvas, div.course-wrapper section.course-content div#seq_content section.problem canvas, div.course-wrapper section.course-content .problem-set section.problem img, div.course-wrapper section.course-content section.problems-wrapper section.problem img, div.course-wrapper section.course-content div#seq_content section.problem img, div.course-wrapper section.course-content ol.vert-mod > li section.problem img, div.course-wrapper section.course-content section.problems-wrapper section.problem img, div.course-wrapper section.course-content div#seq_content section.problem img { + page-break-inside: avoid; } } + div.course-wrapper section.course-content .problem-set section.problem span.unanswered, div.course-wrapper section.course-content section.problems-wrapper section.problem span.unanswered, div.course-wrapper section.course-content div#seq_content section.problem span.unanswered, div.course-wrapper section.course-content ol.vert-mod > li section.problem span.unanswered, div.course-wrapper section.course-content section.problems-wrapper section.problem span.unanswered, div.course-wrapper section.course-content div#seq_content section.problem span.unanswered, div.course-wrapper section.course-content .problem-set section.problem span.ui-icon-bullet, div.course-wrapper section.course-content section.problems-wrapper section.problem span.ui-icon-bullet, div.course-wrapper section.course-content div#seq_content section.problem span.ui-icon-bullet, div.course-wrapper section.course-content ol.vert-mod > li section.problem span.ui-icon-bullet, div.course-wrapper section.course-content section.problems-wrapper section.problem span.ui-icon-bullet, div.course-wrapper section.course-content div#seq_content section.problem span.ui-icon-bullet { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/unanswered-icon.png") center center no-repeat; + height: 14px; + position: relative; + top: 4px; + width: 14px; } + div.course-wrapper section.course-content .problem-set section.problem span.correct, div.course-wrapper section.course-content section.problems-wrapper section.problem span.correct, div.course-wrapper section.course-content div#seq_content section.problem span.correct, div.course-wrapper section.course-content ol.vert-mod > li section.problem span.correct, div.course-wrapper section.course-content section.problems-wrapper section.problem span.correct, div.course-wrapper section.course-content div#seq_content section.problem span.correct, div.course-wrapper section.course-content .problem-set section.problem span.ui-icon-check, div.course-wrapper section.course-content section.problems-wrapper section.problem span.ui-icon-check, div.course-wrapper section.course-content div#seq_content section.problem span.ui-icon-check, div.course-wrapper section.course-content ol.vert-mod > li section.problem span.ui-icon-check, div.course-wrapper section.course-content section.problems-wrapper section.problem span.ui-icon-check, div.course-wrapper section.course-content div#seq_content section.problem span.ui-icon-check { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/correct-icon.png") center center no-repeat; + height: 20px; + position: relative; + top: 6px; + width: 25px; } + div.course-wrapper section.course-content .problem-set section.problem span.incorrect, div.course-wrapper section.course-content section.problems-wrapper section.problem span.incorrect, div.course-wrapper section.course-content div#seq_content section.problem span.incorrect, div.course-wrapper section.course-content ol.vert-mod > li section.problem span.incorrect, div.course-wrapper section.course-content section.problems-wrapper section.problem span.incorrect, div.course-wrapper section.course-content div#seq_content section.problem span.incorrect, div.course-wrapper section.course-content .problem-set section.problem span.ui-icon-close, div.course-wrapper section.course-content section.problems-wrapper section.problem span.ui-icon-close, div.course-wrapper section.course-content div#seq_content section.problem span.ui-icon-close, div.course-wrapper section.course-content ol.vert-mod > li section.problem span.ui-icon-close, div.course-wrapper section.course-content section.problems-wrapper section.problem span.ui-icon-close, div.course-wrapper section.course-content div#seq_content section.problem span.ui-icon-close { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/incorrect-icon.png") center center no-repeat; + height: 20px; + width: 20px; + position: relative; + top: 6px; } + div.course-wrapper section.course-content .problem-set div > span, div.course-wrapper section.course-content section.problems-wrapper div > span, div.course-wrapper section.course-content div#seq_content div > span, div.course-wrapper section.course-content ol.vert-mod > li div > span, div.course-wrapper section.course-content section.problems-wrapper div > span, div.course-wrapper section.course-content div#seq_content div > span { + display: block; + margin-bottom: 11.326px; } + div.course-wrapper section.course-content .problem-set div > span[answer], div.course-wrapper section.course-content section.problems-wrapper div > span[answer], div.course-wrapper section.course-content div#seq_content div > span[answer], div.course-wrapper section.course-content ol.vert-mod > li div > span[answer], div.course-wrapper section.course-content section.problems-wrapper div > span[answer], div.course-wrapper section.course-content div#seq_content div > span[answer] { + border-top: 1px solid #ededed; + border-bottom: 1px solid #ededed; + background: #f3f3f3; + margin: 0 -22.652px; + padding: 11.326px 22.652px; } + div.course-wrapper section.course-content .problem-set input[type="text"], div.course-wrapper section.course-content section.problems-wrapper input[type="text"], div.course-wrapper section.course-content div#seq_content input[type="text"], div.course-wrapper section.course-content ol.vert-mod > li input[type="text"], div.course-wrapper section.course-content section.problems-wrapper input[type="text"], div.course-wrapper section.course-content div#seq_content input[type="text"] { + display: inline-block; + width: 50%; } + div.course-wrapper section.course-content .problem-set center, div.course-wrapper section.course-content section.problems-wrapper center, div.course-wrapper section.course-content div#seq_content center, div.course-wrapper section.course-content ol.vert-mod > li center, div.course-wrapper section.course-content section.problems-wrapper center, div.course-wrapper section.course-content div#seq_content center { + display: block; + margin: 22.652px 0; + border: 1px solid #ccc; + padding: 22.652px; } + div.course-wrapper section.course-content .problem-set section.action, div.course-wrapper section.course-content section.problems-wrapper section.action, div.course-wrapper section.course-content div#seq_content section.action, div.course-wrapper section.course-content ol.vert-mod > li section.action, div.course-wrapper section.course-content section.problems-wrapper section.action, div.course-wrapper section.course-content div#seq_content section.action { + margin-top: 22.652px; } + div.course-wrapper section.course-content .problem-set section.action input[type="button"], div.course-wrapper section.course-content section.problems-wrapper section.action input[type="button"], div.course-wrapper section.course-content div#seq_content section.action input[type="button"], div.course-wrapper section.course-content ol.vert-mod > li section.action input[type="button"], div.course-wrapper section.course-content section.problems-wrapper section.action input[type="button"], div.course-wrapper section.course-content div#seq_content section.action input[type="button"] { + padding: 9.061px 22.652px; + text-shadow: 0 -1px 0 #666666; } + div.course-wrapper section.course-content section.problems-wrapper { + display: table; + width: 100%; } + @media screen and (max-width:1120px) { + div.course-wrapper section.course-content section.problems-wrapper { + display: block; + width: auto; } } + div.course-wrapper section.course-content div#seq_content h1 { + background: none; + margin-bottom: 22.652px; + padding-bottom: 0; + border-bottom: none; } + div.course-wrapper section.course-content ol.vert-mod > li { + border-bottom: 1px solid #ddd; + margin-bottom: 15px; + padding: 0 0 15px; } + div.course-wrapper section.course-content ol.vert-mod > li header { + -webkit-border-radius: 0 4px 0 0; + -moz-border-radius: 0 4px 0 0; + -ms-border-radius: 0 4px 0 0; + -o-border-radius: 0 4px 0 0; + border-radius: 0 4px 0 0; + margin-bottom: -16px; } + div.course-wrapper section.course-content ol.vert-mod > li header h1 { + margin: 0; } + div.course-wrapper section.course-content ol.vert-mod > li header h2 { + float: right; + margin-right: 0; + margin-top: 8px; + text-align: right; + padding-right: 0; + border-right: 0; } + div.course-wrapper section.course-content ol.vert-mod > li:last-child { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; } + div.course-wrapper section.course-content ol.vert-mod > li .histogram { + width: 200px; + height: 150px; } + div.course-wrapper section.course-content ol.vert-mod > li ul { + list-style: disc outside none; + padding-left: 1em; } + div.course-wrapper section.course-content ol.vert-mod > li nav.sequence-bottom ul { + list-style: none; + padding: 0; } + div.course-wrapper section.course-content section.tutorials h2 { + margin-bottom: 22.652px; } + div.course-wrapper section.course-content section.tutorials ul { + margin: 0; + zoom: 1; } + div.course-wrapper section.course-content section.tutorials ul:before, div.course-wrapper section.course-content section.tutorials ul:after { + content: ""; + display: table; } + div.course-wrapper section.course-content section.tutorials ul:after { + clear: both; } + div.course-wrapper section.course-content section.tutorials ul li { + width: 31.522%; + float: left; + margin-right: 2.717%; + margin-bottom: 22.652px; } + div.course-wrapper section.course-content section.tutorials ul li:nth-child(3n) { + margin-right: 0; } + div.course-wrapper section.course-content section.tutorials ul li:nth-child(3n+1) { + clear: both; } + div.course-wrapper section.course-content section.tutorials ul li a { + font-weight: bold; } + div.course-wrapper section.course-content div.staff_info { + zoom: 1; + white-space: pre-wrap; + border-top: 1px solid #ccc; + padding-top: 22.652px; + margin-top: 22.652px; + line-height: 22.652px; + font-family: Consolas, "Lucida Console", Monaco, "Courier New", Courier, monospace; } + div.course-wrapper section.course-content div.staff_info:before, div.course-wrapper section.course-content div.staff_info:after { + content: ""; + display: table; } + div.course-wrapper section.course-content div.staff_info:after { + clear: both; } + div.course-wrapper section.course-content div.ui-slider { + border: 1px solid #aaa; + background: #ddd; + -webkit-box-shadow: compact(inset 0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #eeeeee, false, false, false, false, false, false, false, false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; } + div.course-wrapper section.course-content div.ui-slider a.ui-slider-handle { + -webkit-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + background: #993333 url(../images/slider-bars.png) center center no-repeat; + border: 1px solid #4d1919; + cursor: pointer; } + div.course-wrapper section.course-content div.ui-slider a.ui-slider-handle:hover, div.course-wrapper section.course-content div.ui-slider a.ui-slider-handle:focus { + background-color: #bf4040; + outline: none; } + div.course-wrapper section.course-content div.ui-tabs { + border: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + margin: 0; + padding: 0; } + div.course-wrapper section.course-content div.ui-tabs .ui-tabs-nav { + background: none; + border: 0; + margin-bottom: 11.326px; } + div.course-wrapper section.course-content div.ui-tabs .ui-tabs-panel { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + padding: 0; } +div.course-wrapper.closed section.course-index { + width: 3.077%; + overflow: hidden; } + div.course-wrapper.closed section.course-index header#open_close_accordion { + padding: 0; + min-height: 47px; } + div.course-wrapper.closed section.course-index header#open_close_accordion a { + background-image: url("../images/slide-right-icon.png"); } + div.course-wrapper.closed section.course-index header#open_close_accordion h2 { + visibility: hidden; + width: 10px; } + div.course-wrapper.closed section.course-index div#accordion { + visibility: hidden; + width: 10px; + padding: 0; } + div.course-wrapper.closed section.course-index div#accordion nav { + white-space: pre; + overflow: hidden; } + div.course-wrapper.closed section.course-index div#accordion nav ul { + overflow: hidden; + white-space: nowrap; } +div.course-wrapper.closed section.course-content { + width: 97.773%; } + +nav.sequence-nav { + border-bottom: 1px solid #e4d080; + margin-bottom: 22.652px; + position: relative; + -webkit-border-top-right-radius: 4px; + -moz-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + -ms-border-top-right-radius: 4px; + -o-border-top-right-radius: 4px; + border-top-right-radius: 4px; } + nav.sequence-nav ol { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table; + height: 100%; + padding-right: 8.696%; + width: 100%; } + nav.sequence-nav ol li { + border-left: 1px solid #e4d080; + display: table-cell; + min-width: 20px; } + nav.sequence-nav ol li:first-child { + border-left: none; } + nav.sequence-nav ol li .inactive, nav.sequence-nav ol li a.seq_video_inactive, nav.sequence-nav ol li a.seq_other_inactive, nav.sequence-nav ol li a.seq_vertical_inactive, nav.sequence-nav ol li a.seq_problem_inactive { + background-repeat: no-repeat; } + nav.sequence-nav ol li .inactive:hover, nav.sequence-nav ol li a.seq_video_inactive:hover, nav.sequence-nav ol li a.seq_other_inactive:hover, nav.sequence-nav ol li a.seq_vertical_inactive:hover, nav.sequence-nav ol li a.seq_problem_inactive:hover { + background-color: #f9f4e1; } + nav.sequence-nav ol li .visited, nav.sequence-nav ol li a.seq_video_visited, nav.sequence-nav ol li a.seq_other_visited, nav.sequence-nav ol li a.seq_vertical_visited, nav.sequence-nav ol li a.seq_problem_visited { + background-color: #DCCDA2; + background-repeat: no-repeat; + -webkit-box-shadow: compact(inset 0 0 3px #ceb97d, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 3px #ceb97d, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 3px #ceb97d, false, false, false, false, false, false, false, false); } + nav.sequence-nav ol li .visited:hover, nav.sequence-nav ol li a.seq_video_visited:hover, nav.sequence-nav ol li a.seq_other_visited:hover, nav.sequence-nav ol li a.seq_vertical_visited:hover, nav.sequence-nav ol li a.seq_problem_visited:hover { + background-color: #f6efd4; + background-position: center center; } + nav.sequence-nav ol li .active, nav.sequence-nav ol li a.seq_video_active, nav.sequence-nav ol li a.seq_other_active, nav.sequence-nav ol li a.seq_vertical_active, nav.sequence-nav ol li a.seq_problem_active, nav.sequence-nav ol li section.course-index div#accordion h3.ui-accordion-header.ui-state-active, section.course-index div#accordion nav.sequence-nav ol li h3.ui-accordion-header.ui-state-active { + background-color: #fff; + background-repeat: no-repeat; + -webkit-box-shadow: compact(0 1px 0 white, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 white, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 white, false, false, false, false, false, false, false, false); } + nav.sequence-nav ol li .active:hover, nav.sequence-nav ol li a.seq_video_active:hover, nav.sequence-nav ol li a.seq_other_active:hover, nav.sequence-nav ol li a.seq_vertical_active:hover, nav.sequence-nav ol li a.seq_problem_active:hover, nav.sequence-nav ol li section.course-index div#accordion h3.ui-accordion-header.ui-state-active:hover, section.course-index div#accordion nav.sequence-nav ol li h3.ui-accordion-header.ui-state-active:hover { + background-color: #fff; + background-position: center; } + nav.sequence-nav ol li a { + background-position: center center; + border: none; + cursor: pointer; + display: block; + height: 17px; + padding: 15px 0 14px; + position: relative; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.4s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.4s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.4s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.4s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.4s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + width: 100%; } + nav.sequence-nav ol li a.progress, nav.sequence-nav ol li a.progress-none, nav.sequence-nav ol li a.progress-some, nav.sequence-nav ol li a.progress-done { + border-bottom-style: solid; + border-bottom-width: 4px; } + nav.sequence-nav ol li a.progress-none { + border-bottom-color: red; } + nav.sequence-nav ol li a.progress-some { + border-bottom-color: yellow; } + nav.sequence-nav ol li a.progress-done { + border-bottom-color: green; } + nav.sequence-nav ol li a.seq_video_inactive { + background-image: url("../images/sequence-nav/video-icon-normal.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_video_visited { + background-image: url("../images/sequence-nav/video-icon-visited.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_video_active { + background-image: url("../images/sequence-nav/video-icon-current.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_other_inactive { + background-image: url("../images/sequence-nav/document-icon-normal.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_other_visited { + background-image: url("../images/sequence-nav/document-icon-visited.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_other_active { + background-image: url("../images/sequence-nav/document-icon-current.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_vertical_inactive, nav.sequence-nav ol li a.seq_problem_inactive { + background-image: url("../images/sequence-nav/list-icon-normal.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_vertical_visited, nav.sequence-nav ol li a.seq_problem_visited { + background-image: url("../images/sequence-nav/list-icon-visited.png"); + background-position: center; } + nav.sequence-nav ol li a.seq_vertical_active, nav.sequence-nav ol li a.seq_problem_active { + background-image: url("../images/sequence-nav/list-icon-current.png"); + background-position: center; } + nav.sequence-nav ol li a p { + background: #333; + color: #fff; + display: none; + line-height: 22.652px; + left: 0px; + opacity: 0; + padding: 6px; + position: absolute; + top: 48px; + text-shadow: 0 -1px 0 black; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.1s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(cubic-bezier(0.77, 0, 0.175, 1), false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(cubic-bezier(0.77, 0, 0.175, 1), false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(cubic-bezier(0.77, 0, 0.175, 1), false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(cubic-bezier(0.77, 0, 0.175, 1), false, false, false, false, false, false, false, false); + transition-timing-function: compact(cubic-bezier(0.77, 0, 0.175, 1), false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + white-space: pre; + z-index: 99; } + nav.sequence-nav ol li a p:empty { + background: none; } + nav.sequence-nav ol li a p:empty::after { + display: none; } + nav.sequence-nav ol li a p::after { + background: #333; + content: " "; + display: block; + height: 10px; + left: 18px; + position: absolute; + top: -5px; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + width: 10px; } + nav.sequence-nav ol li a:hover p { + display: block; + margin-top: 4px; + opacity: 1; } + nav.sequence-nav ul { + list-style: none; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 8.696%; } + nav.sequence-nav ul li { + float: left; + width: 50%; } + nav.sequence-nav ul li.prev a, nav.sequence-nav ul li.next a { + background-color: #f2e7bf; + background-position: center center; + background-repeat: no-repeat; + border-left: 1px solid #e4d080; + -webkit-box-shadow: compact(inset 1px 0 0 #faf7e9, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 1px 0 0 #faf7e9, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 1px 0 0 #faf7e9, false, false, false, false, false, false, false, false); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; + display: block; + text-indent: -9999px; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + nav.sequence-nav ul li.prev a:hover, nav.sequence-nav ul li.next a:hover { + opacity: .5; } + nav.sequence-nav ul li.prev a.disabled, nav.sequence-nav ul li.next a.disabled { + cursor: normal; + opacity: .4; } + nav.sequence-nav ul li.prev a { + background-image: url("../images/sequence-nav/previous-icon.png"); } + nav.sequence-nav ul li.prev a:hover { + background-color: #f6efd4; } + nav.sequence-nav ul li.next a { + background-image: url("../images/sequence-nav/next-icon.png"); } + nav.sequence-nav ul li.next a:hover { + background-color: #f6efd4; } + body.touch-based-device nav.sequence-nav ol li a:hover p { + display: none; } + +section.course-content { + position: relative; } + section.course-content ol.vert-mod nav.sequence-nav { + margin-top: -15px; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; } + section.course-content nav.sequence-bottom { + margin: 45.304px 0 0; + text-align: center; } + section.course-content nav.sequence-bottom ul { + background-color: #f2e7bf; + background-color: #f2e7bf; + border: 1px solid #e4d080; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: compact(inset 0 0 0 1px #faf7e9, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 0 1px #faf7e9, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 0 1px #faf7e9, false, false, false, false, false, false, false, false); + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.course-content nav.sequence-bottom ul li { + float: left; } + section.course-content nav.sequence-bottom ul li.prev, section.course-content nav.sequence-bottom ul li.next { + margin-bottom: 0; } + section.course-content nav.sequence-bottom ul li.prev a, section.course-content nav.sequence-bottom ul li.next a { + background-position: center center; + background-repeat: no-repeat; + border-bottom: none; + display: block; + padding: 11.326px 4px; + text-indent: -9999px; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + transition-timing-function: compact(cubic-bezier(0.455, 0.03, 0.515, 0.955), false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + width: 45px; } + section.course-content nav.sequence-bottom ul li.prev a:hover, section.course-content nav.sequence-bottom ul li.next a:hover { + background-color: #f6efd4; + color: #7e691a; + opacity: .5; + text-decoration: none; } + section.course-content nav.sequence-bottom ul li.prev a.disabled, section.course-content nav.sequence-bottom ul li.next a.disabled { + background-color: #fffffe; + opacity: .4; } + section.course-content nav.sequence-bottom ul li.prev a { + background-image: url("../images/sequence-nav/previous-icon.png"); + border-right: 1px solid #e4d080; } + section.course-content nav.sequence-bottom ul li.prev a:hover { + background-color: none; } + section.course-content nav.sequence-bottom ul li.next a { + background-image: url("../images/sequence-nav/next-icon.png"); } + section.course-content nav.sequence-bottom ul li.next a:hover { + background-color: none; } + +section.course-index header { + max-height: 47px; } + section.course-index header h2 { + white-space: nowrap; } +section.course-index div#accordion h3 { + -webkit-box-shadow: compact(inset 0 1px 0 0 #eeeeee, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #eeeeee, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #eeeeee, false, false, false, false, false, false, false, false); + border-top: 1px solid #d3d3d3; + overflow: hidden; + margin: 0; } + section.course-index div#accordion h3:first-child { + border: none; } + section.course-index div#accordion h3:hover { + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); } + section.course-index div#accordion h3.ui-accordion-header { + color: #000; } + section.course-index div#accordion h3.ui-accordion-header a { + font-size: 14px; + color: #4d4d4d; } + section.course-index div#accordion h3.ui-accordion-header.ui-state-active { + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e1e1e1, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + border-bottom: 1px solid #d3d3d3; } +section.course-index div#accordion ul.ui-accordion-content { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: compact(inset -1px 0 0 #e6e6e6, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset -1px 0 0 #e6e6e6, false, false, false, false, false, false, false, false); + box-shadow: compact(inset -1px 0 0 #e6e6e6, false, false, false, false, false, false, false, false); + background: #dadada; + border: none; + font-size: 12px; + margin: 0; + padding: 1em 1.5em; } + section.course-index div#accordion ul.ui-accordion-content li { + margin-bottom: 11.326px; } + section.course-index div#accordion ul.ui-accordion-content li a { + border: 1px solid transparent; + background: transparent; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + border-radius: 4px; + position: relative; + padding: 5px 36px 5px 10px; + text-decoration: none; + display: block; + color: #666; } + section.course-index div#accordion ul.ui-accordion-content li a p { + font-weight: bold; + margin-bottom: 0; } + section.course-index div#accordion ul.ui-accordion-content li a p span.subtitle { + color: #666; + font-weight: normal; + display: block; } + section.course-index div#accordion ul.ui-accordion-content li a:after { + background: transparent; + border-top: 1px solid #b4b4b4; + border-right: 1px solid #b4b4b4; + content: ""; + display: block; + height: 12px; + margin-top: -6px; + opacity: 0; + position: absolute; + top: 50%; + right: 30px; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + width: 12px; } + section.course-index div#accordion ul.ui-accordion-content li a:hover { + background-image: compact(linear, compact(-90deg, rgba(245, 245, 245, 0.4), rgba(230, 230, 230, 0.4), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, rgba(245, 245, 245, 0.4), rgba(230, 230, 230, 0.4), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, rgba(245, 245, 245, 0.4), rgba(230, 230, 230, 0.4), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, rgba(245, 245, 245, 0.4), rgba(230, 230, 230, 0.4), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, rgba(245, 245, 245, 0.4), rgba(230, 230, 230, 0.4), false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + border-color: #c8c8c8; } + section.course-index div#accordion ul.ui-accordion-content li a:hover:after { + opacity: 1; + right: 15px; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.2s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + transition-timing-function: compact(linear, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + section.course-index div#accordion ul.ui-accordion-content li a:hover > a p { + color: #333; } + section.course-index div#accordion ul.ui-accordion-content li a:active { + -webkit-box-shadow: compact(inset 0 1px 14px 0 rgba(0, 0, 0, 0.1), false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 14px 0 rgba(0, 0, 0, 0.1), false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 14px 0 rgba(0, 0, 0, 0.1), false, false, false, false, false, false, false, false); } + section.course-index div#accordion ul.ui-accordion-content li a:active:after { + opacity: 1; + right: 15px; } + section.course-index div#accordion ul.ui-accordion-content li.active { + font-weight: bold; } + section.course-index div#accordion ul.ui-accordion-content li.active > a { + background: #f0f0f0; + background-image: compact(linear, compact(-90deg, #f5f5f5, #e6e6e6, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e6e6e6, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e6e6e6, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e6e6e6, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + background-image: compact(linear, compact(-90deg, #f5f5f5, #e6e6e6, false, false, false, false, false, false, false, false), false, false, false, false, false, false, false, false, false); + border-color: #c8c8c8; } + section.course-index div#accordion ul.ui-accordion-content li.active > a:after { + opacity: 1; + right: 15px; } + section.course-index div#accordion ul.ui-accordion-content li.active > a p { + color: #333; } + section.course-index div#accordion ul.ui-accordion-content li.active span.subtitle { + font-weight: normal; } + +@-moz-document url-prefix() { + a.add-fullscreen { + display: none !important; } } + +section.course-content .dullify, section.course-content div.video article.video-wrapper section.video-controls ul.vcr, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls { + opacity: .4; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + section.course-content .dullify:hover, section.course-content div.video article.video-wrapper section.video-controls ul.vcr:hover, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls:hover { + opacity: 1; } +section.course-content div.video { + zoom: 1; + background: #f3f3f3; + border-bottom: 1px solid #e1e1e1; + border-top: 1px solid #e1e1e1; + display: block; + margin: 0 -22.652px; + padding: 6px 22.652px; } + section.course-content div.video:before, section.course-content div.video:after { + content: ""; + display: table; } + section.course-content div.video:after { + clear: both; } + section.course-content div.video article.video-wrapper { + float: left; + margin-right: 2.717%; + width: 65.761%; } + section.course-content div.video article.video-wrapper section.video-player { + height: 0; + overflow: hidden; + padding-bottom: 56.25%; + padding-top: 30px; + position: relative; } + section.course-content div.video article.video-wrapper section.video-player object, section.course-content div.video article.video-wrapper section.video-player iframe { + border: none; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; } + section.course-content div.video article.video-wrapper section.video-controls { + background: #333; + border: 1px solid #000; + border-top: 0; + color: #ccc; + position: relative; } + section.course-content div.video article.video-wrapper section.video-controls:hover ul, section.course-content div.video article.video-wrapper section.video-controls:hover div { + opacity: 1; } + section.course-content div.video article.video-wrapper section.video-controls div.slider { + background: #c2c2c2; + border: none; + border-bottom: 1px solid #000; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + border-top: 1px solid #000; + -webkit-box-shadow: compact(inset 0 1px 0 #eeeeee, 0 1px 0 #555555, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #eeeeee, 0 1px 0 #555555, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #eeeeee, 0 1px 0 #555555, false, false, false, false, false, false, false); + height: 7px; + -webkit-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -o-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + section.course-content div.video article.video-wrapper section.video-controls div.slider div.ui-widget-header { + background: #777; + -webkit-box-shadow: compact(inset 0 1px 0 #999999, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #999999, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #999999, false, false, false, false, false, false, false, false); } + section.course-content div.video article.video-wrapper section.video-controls div.slider .ui-tooltip.qtip .ui-tooltip-content { + background: #993333; + border: 1px solid #4d1919; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + -o-border-radius: 2px; + border-radius: 2px; + -webkit-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + color: #fff; + font: bold 12px "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + margin-bottom: 6px; + margin-right: 0; + overflow: visible; + padding: 4px; + text-align: center; + text-shadow: 0 -1px 0 #732626; + -webkit-font-smoothing: antialiased; } + section.course-content div.video article.video-wrapper section.video-controls div.slider .ui-tooltip.qtip .ui-tooltip-content::after { + background: #993333; + border-bottom: 1px solid #4d1919; + border-right: 1px solid #4d1919; + bottom: -5px; + content: " "; + display: block; + height: 7px; + left: 50%; + margin-left: -3px; + position: absolute; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + width: 7px; } + section.course-content div.video article.video-wrapper section.video-controls div.slider a.ui-slider-handle { + background: #993333 url(../images/slider-handle.png) center center no-repeat; + -webkit-background-size: compact(50%, false, false, false, false, false, false, false, false); + -moz-background-size: compact(50%, false, false, false, false, false, false, false, false); + -ms-background-size: compact(50%, false, false, false, false, false, false, false, false); + -o-background-size: compact(50%, false, false, false, false, false, false, false, false); + background-size: compact(50%, false, false, false, false, false, false, false, false); + border: 1px solid #4d1919; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + -ms-border-radius: 15px; + -o-border-radius: 15px; + border-radius: 15px; + -webkit-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + cursor: pointer; + height: 15px; + margin-left: -7px; + top: -4px; + -webkit-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -o-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + width: 15px; } + section.course-content div.video article.video-wrapper section.video-controls div.slider a.ui-slider-handle:focus, section.course-content div.video article.video-wrapper section.video-controls div.slider a.ui-slider-handle:hover { + background-color: #bf4040; + outline: none; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr { + float: left; + list-style: none; + margin-right: 22.652px; + padding: 0; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li { + float: left; + margin-bottom: 0; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li a { + border-bottom: none; + border-right: 1px solid #000; + -webkit-box-shadow: compact(1px 0 0 #555555, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(1px 0 0 #555555, false, false, false, false, false, false, false, false); + box-shadow: compact(1px 0 0 #555555, false, false, false, false, false, false, false, false); + cursor: pointer; + display: block; + line-height: 46px; + padding: 0 16.989px; + text-indent: -9999px; + -webkit-transition-property: compact(background-color, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(background-color, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(background-color, false, false, false, false, false, false, false, false); + -o-transition-property: compact(background-color, false, false, false, false, false, false, false, false); + transition-property: compact(background-color, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(opacity, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(opacity, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(opacity, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(opacity, false, false, false, false, false, false, false, false); + transition-duration: compact(opacity, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + width: 14px; + background: url("../images/vcr.png") 15px 15px no-repeat; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li a:empty { + height: 46px; + background: url("../images/vcr.png") 15px 15px no-repeat; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li a.play { + background-position: 17px -114px; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li a.play:hover { + background-color: #444; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li a.pause { + background-position: 16px -50px; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li a.pause:hover { + background-color: #444; } + section.course-content div.video article.video-wrapper section.video-controls ul.vcr li div.vidtime { + padding-left: 16.989px; + font-weight: bold; + line-height: 46px; + padding-left: 16.989px; + -webkit-font-smoothing: antialiased; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls { + float: right; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds { + float: left; + position: relative; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds.open > a { + background: url("../images/open-arrow.png") 10px center no-repeat; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds.open ol.video_speeds { + display: block; + opacity: 1; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a { + background: url("../images/closed-arrow.png") 10px center no-repeat; + border-left: 1px solid #000; + border-right: 1px solid #000; + -webkit-box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + -moz-box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + zoom: 1; + color: #fff; + cursor: pointer; + display: block; + line-height: 46px; + margin-right: 0; + padding-left: 15px; + position: relative; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + -webkit-font-smoothing: antialiased; + width: 110px; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a:before, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a:after { + content: ""; + display: table; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a:after { + clear: both; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a h3 { + color: #999; + float: left; + font-size: 12px; + font-weight: normal; + letter-spacing: 1px; + padding: 0 5.663px 0 11.326px; + text-transform: uppercase; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a p.active { + float: left; + font-weight: bold; + margin-bottom: 0; + padding: 0 11.326px 0 0; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a:hover, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a:active, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds > a:focus { + opacity: 1; + background-color: #444; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds ol.video_speeds { + -webkit-box-shadow: compact(inset 1px 0 0 #555555, 0 3px 0 #444444, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 1px 0 0 #555555, 0 3px 0 #444444, false, false, false, false, false, false, false); + box-shadow: compact(inset 1px 0 0 #555555, 0 3px 0 #444444, false, false, false, false, false, false, false); + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + background-color: #444; + border: 1px solid #000; + bottom: 46px; + display: none; + opacity: 0; + position: absolute; + width: 125px; + z-index: 10; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds ol.video_speeds li { + -webkit-box-shadow: compact(0 1px 0 #555555, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 #555555, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 #555555, false, false, false, false, false, false, false, false); + border-bottom: 1px solid #000; + color: #fff; + cursor: pointer; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds ol.video_speeds li a { + border: 0; + color: #fff; + display: block; + padding: 11.326px; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds ol.video_speeds li a:hover { + background-color: #666; + color: #aaa; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds ol.video_speeds li.active { + font-weight: bold; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.speeds ol.video_speeds li:last-child { + -webkit-box-shadow: compact(none, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(none, false, false, false, false, false, false, false, false); + box-shadow: compact(none, false, false, false, false, false, false, false, false); + border-bottom: 0; + margin-top: 0; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume { + float: left; + position: relative; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume.open .volume-slider-container { + display: block; + opacity: 1; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume.muted > a { + background: url("../images/mute.png") 10px center no-repeat; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume > a { + background: url("../images/volume.png") 10px center no-repeat; + border-right: 1px solid #000; + -webkit-box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + -moz-box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + zoom: 1; + color: #fff; + cursor: pointer; + display: block; + height: 46px; + margin-right: 0; + padding-left: 15px; + position: relative; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + -webkit-font-smoothing: antialiased; + width: 30px; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume > a:before, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume > a:after { + content: ""; + display: table; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume > a:after { + clear: both; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume > a:hover, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume > a:active, section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume > a:focus { + background-color: #444; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume .volume-slider-container { + -webkit-box-shadow: compact(inset 1px 0 0 #555555, 0 3px 0 #444444, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 1px 0 0 #555555, 0 3px 0 #444444, false, false, false, false, false, false, false); + box-shadow: compact(inset 1px 0 0 #555555, 0 3px 0 #444444, false, false, false, false, false, false, false); + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + background-color: #444; + border: 1px solid #000; + bottom: 46px; + display: none; + opacity: 0; + position: absolute; + width: 45px; + height: 125px; + margin-left: -1px; + z-index: 10; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume .volume-slider-container .volume-slider { + height: 100px; + border: 0; + width: 5px; + margin: 14px auto; + background: #666; + border: 1px solid #000; + -webkit-box-shadow: compact(0 1px 0 #333333, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 #333333, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 #333333, false, false, false, false, false, false, false, false); } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume .volume-slider-container .volume-slider a.ui-slider-handle { + background: #993333 url(../images/slider-handle.png) center center no-repeat; + -webkit-background-size: compact(50%, false, false, false, false, false, false, false, false); + -moz-background-size: compact(50%, false, false, false, false, false, false, false, false); + -ms-background-size: compact(50%, false, false, false, false, false, false, false, false); + -o-background-size: compact(50%, false, false, false, false, false, false, false, false); + background-size: compact(50%, false, false, false, false, false, false, false, false); + border: 1px solid #4d1919; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + -ms-border-radius: 15px; + -o-border-radius: 15px; + border-radius: 15px; + -webkit-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #bf4040, false, false, false, false, false, false, false, false); + cursor: pointer; + height: 15px; + left: -6px; + -webkit-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -o-transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + transition-property: compact(height, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + transition-duration: compact(width, 2s, ease-in-out, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + width: 15px; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls div.volume .volume-slider-container .volume-slider .ui-slider-range { + background: #ddd; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls a.add-fullscreen { + background: url(../images/fullscreen.png) center no-repeat; + border-right: 1px solid #000; + -webkit-box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + -moz-box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + box-shadow: compact(1px 0 0 #555555, inset 1px 0 0 #555555, false, false, false, false, false, false, false); + color: #797979; + display: block; + float: left; + line-height: 46px; + margin-left: 0; + padding: 0 11.326px; + text-indent: -9999px; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + width: 30px; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls a.add-fullscreen:hover { + background-color: #444; + color: #fff; + text-decoration: none; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls a.hide-subtitles { + background: url("../images/cc.png") center no-repeat; + color: #797979; + display: block; + float: left; + font-weight: 800; + line-height: 46px; + margin-left: 0; + opacity: 1; + padding: 0 11.326px; + position: relative; + text-indent: -9999px; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + -webkit-font-smoothing: antialiased; + width: 30px; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls a.hide-subtitles:hover { + background-color: #444; + color: #fff; + text-decoration: none; } + section.course-content div.video article.video-wrapper section.video-controls div.secondary-controls a.hide-subtitles.off { + opacity: .7; } + section.course-content div.video article.video-wrapper:hover section.video-controls ul, section.course-content div.video article.video-wrapper:hover section.video-controls div { + opacity: 1; } + section.course-content div.video article.video-wrapper:hover section.video-controls div.slider { + height: 14px; + margin-top: -7px; } + section.course-content div.video article.video-wrapper:hover section.video-controls div.slider a.ui-slider-handle { + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + -ms-border-radius: 20px; + -o-border-radius: 20px; + border-radius: 20px; + height: 20px; + margin-left: -10px; + top: -4px; + width: 20px; } + section.course-content div.video ol.subtitles { + float: left; + max-height: 460px; + overflow: auto; + width: 31.522%; } + section.course-content div.video ol.subtitles li { + border: 0; + color: #666; + cursor: pointer; + margin-bottom: 8px; + padding: 0; } + section.course-content div.video ol.subtitles li.current { + color: #333; + font-weight: 700; } + section.course-content div.video ol.subtitles li:hover { + color: #993333; } + section.course-content div.video ol.subtitles li:empty { + margin-bottom: 0px; } + section.course-content div.video.closed article.video-wrapper { + width: 100%; } + section.course-content div.video.closed ol.subtitles { + width: 0px; } + section.course-content div.video.fullscreen { + background: rgba(0, 0, 0, 0.95); + border: 0; + bottom: 0; + height: 100%; + left: 0; + margin: 0; + max-height: 100%; + overflow: hidden; + padding: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 999; } + section.course-content div.video.fullscreen.closed ol.subtitles { + right: -31.984%; + width: auto; } + section.course-content div.video.fullscreen a.exit { + color: #aaa; + display: none; + font-style: 12px; + left: 20px; + letter-spacing: 1px; + position: absolute; + text-transform: uppercase; + top: 20px; } + section.course-content div.video.fullscreen a.exit::after { + content: "✖"; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + padding-left: 6px; } + section.course-content div.video.fullscreen a.exit:hover { + color: #993333; } + section.course-content div.video.fullscreen div.tc-wrapper article.video-wrapper { + width: 100%; } + section.course-content div.video.fullscreen div.tc-wrapper object, section.course-content div.video.fullscreen div.tc-wrapper iframe { + bottom: 0; + height: 100%; + left: 0; + overflow: hidden; + position: fixed; + top: 0; } + section.course-content div.video.fullscreen div.tc-wrapper section.video-controls { + bottom: 0; + left: 0; + position: absolute; + width: 100%; + z-index: 9999; } + section.course-content div.video.fullscreen ol.subtitles { + background: rgba(0, 0, 0, 0.8); + bottom: 0; + height: 100%; + max-height: 100%; + max-width: 23.482%; + padding: 22.652px; + position: fixed; + right: 0; + top: 0; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + section.course-content div.video.fullscreen ol.subtitles li { + color: #aaa; } + section.course-content div.video.fullscreen ol.subtitles li.current { + color: #fff; } + +div.course-wrapper.closed section.course-content div.video ol.subtitles { + max-height: 577px; } + +section.tool-wrapper { + background: #073642; + border-bottom: 1px solid #000203; + border-top: 1px solid #000203; + -webkit-box-shadow: compact(inset 0 0 0 4px #084150, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 0 4px #084150, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 0 4px #084150, false, false, false, false, false, false, false, false); + color: #839496; + display: table; + margin: 22.652px -22.652px 0; } + section.tool-wrapper div#graph-container { + background: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; + padding: 22.652px; + vertical-align: top; + width: 51.359%; } + section.tool-wrapper div#graph-container .ui-widget-content { + background: none; + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; } + section.tool-wrapper div#graph-container canvas { + width: 100%; } + section.tool-wrapper div#graph-container ul.ui-tabs-nav { + background: #062e39; + border-bottom: 1px solid #03181d; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + margin: -22.652px -22.652px 0; + padding: 0; + position: relative; + width: 110%; } + section.tool-wrapper div#graph-container ul.ui-tabs-nav li { + background: none; + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + color: #fff; + margin-bottom: 0; } + section.tool-wrapper div#graph-container ul.ui-tabs-nav li.ui-tabs-selected { + background-color: #073642; + border-left: 1px solid #03181d; + border-right: 1px solid #03181d; } + section.tool-wrapper div#graph-container ul.ui-tabs-nav li.ui-tabs-selected:first-child { + border-left: none; } + section.tool-wrapper div#graph-container ul.ui-tabs-nav li.ui-tabs-selected a { + color: #eee8d5; } + section.tool-wrapper div#graph-container ul.ui-tabs-nav li a { + border: none; + color: #839496; + font: bold 12px "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + letter-spacing: 1px; + text-transform: uppercase; } + section.tool-wrapper div#graph-container ul.ui-tabs-nav li a:hover { + color: #eee8d5; } + section.tool-wrapper div#controlls-container { + background: #062e39; + border-right: 1px solid #001317; + -webkit-box-shadow: compact(1px 0 0 #004355, inset 0 0 0 4px #06323d, false, false, false, false, false, false, false); + -moz-box-shadow: compact(1px 0 0 #004355, inset 0 0 0 4px #06323d, false, false, false, false, false, false, false); + box-shadow: compact(1px 0 0 #004355, inset 0 0 0 4px #06323d, false, false, false, false, false, false, false); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; + padding: 22.652px; + vertical-align: top; + width: 48.641%; } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper { + border-bottom: 1px solid #021014; + -webkit-box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + margin-bottom: 22.652px; + padding: 0 0 22.652px; } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton { + border-color: #001317; + border: 1px solid #3d5962; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: compact(inset 0 1px 0 0 #939da0, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #939da0, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #939da0, false, false, false, false, false, false, false, false); + color: white; + display: inline; + font-size: 11px; + font-weight: bold; + background-color: #637c84; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#637c84, #43626b, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#637c84, #43626b, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#637c84, #43626b, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#637c84, #43626b, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#637c84, #43626b, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#637c84, #43626b, false, false, false, false, false, false, false, false)); + padding: 6px 18px 7px; + text-shadow: 0 1px 0 #31505a; + -webkit-background-clip: padding-box; + display: block; + float: right; + font: bold 14px "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton:hover { + -webkit-box-shadow: compact(inset 0 1px 0 0 #778589, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #778589, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #778589, false, false, false, false, false, false, false, false); + cursor: pointer; + background-color: #5c6c71; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#5c6c71, #3e5961, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#5c6c71, #3e5961, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#5c6c71, #3e5961, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#5c6c71, #3e5961, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#5c6c71, #3e5961, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#5c6c71, #3e5961, false, false, false, false, false, false, false, false)); } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton:active { + border: 1px solid #3d5962; + -webkit-box-shadow: compact(inset 0 0 8px 4px #395057, inset 0 0 8px 4px #395057, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 8px 4px #395057, inset 0 0 8px 4px #395057, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 8px 4px #395057, inset 0 0 8px 4px #395057, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton:active { + -webkit-box-shadow: compact(none, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(none, false, false, false, false, false, false, false, false); + box-shadow: compact(none, false, false, false, false, false, false, false, false); } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton[value="Stop"] { + border: 1px solid #030d15; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: compact(inset 0 1px 0 0 #215f8a, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #215f8a, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #215f8a, false, false, false, false, false, false, false, false); + color: white; + display: inline; + font-size: 11px; + font-weight: bold; + background-color: #0f3550; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#0f3550, #041623, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#0f3550, #041623, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#0f3550, #041623, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#0f3550, #041623, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#0f3550, #041623, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#0f3550, #041623, false, false, false, false, false, false, false, false)); + padding: 6px 18px 7px; + text-shadow: 0 1px 0 #000203; + -webkit-background-clip: padding-box; + font: bold 14px "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton[value="Stop"]:hover { + -webkit-box-shadow: compact(inset 0 1px 0 0 #174362, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #174362, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #174362, false, false, false, false, false, false, false, false); + cursor: pointer; + background-color: #0c2739; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#0c2739, #030d15, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#0c2739, #030d15, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#0c2739, #030d15, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#0c2739, #030d15, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#0c2739, #030d15, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#0c2739, #030d15, false, false, false, false, false, false, false, false)); } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton[value="Stop"]:active { + border: 1px solid #030d15; + -webkit-box-shadow: compact(inset 0 0 8px 4px #010507, inset 0 0 8px 4px #010507, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 8px 4px #010507, inset 0 0 8px 4px #010507, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 8px 4px #010507, inset 0 0 8px 4px #010507, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); } + section.tool-wrapper div#controlls-container div.graph-controls div.music-wrapper input#playButton[value="Stop"]:active { + -webkit-box-shadow: compact(none, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(none, false, false, false, false, false, false, false, false); + box-shadow: compact(none, false, false, false, false, false, false, false, false); } + section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper { + border-bottom: 1px solid #021014; + -webkit-box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + zoom: 1; + margin-bottom: 22.652px; + margin-bottom: 22.652px; + padding: 0 0 22.652px; } + section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper:before, section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper:after { + content: ""; + display: table; } + section.tool-wrapper div#controlls-container div.graph-controls div.inputs-wrapper:after { + clear: both; } + section.tool-wrapper div#controlls-container div.graph-controls p { + font-weight: bold; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin: 0; + text-shadow: 0 -1px 0 #021014; + -webkit-font-smoothing: antialiased; } + section.tool-wrapper div#controlls-container div.graph-controls ul { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-bottom: 0; } + section.tool-wrapper div#controlls-container div.graph-controls ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-bottom: 0; } + section.tool-wrapper div#controlls-container div.graph-controls ul li input { + margin-right: 5px; } + section.tool-wrapper div#controlls-container div.graph-controls div#graph-listen { + display: block; + float: left; + margin-bottom: 0; + margin-right: 20px; + margin-top: 8px; + text-align: right; } + section.tool-wrapper div#controlls-container label { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + -o-border-radius: 2px; + border-radius: 2px; + color: #fff; + font-weight: bold; + padding: 3px; + -webkit-font-smoothing: antialiased; } + section.tool-wrapper div#controlls-container label[for="vinCheckbox"], section.tool-wrapper div#controlls-container label[for="vinRadioButton"] { + color: #409fbf; } + section.tool-wrapper div#controlls-container label[for="voutCheckbox"], section.tool-wrapper div#controlls-container label[for="voutRadioButton"] { + color: #e1a600; } + section.tool-wrapper div#controlls-container label[for="vrCheckbox"], section.tool-wrapper div#controlls-container label[for="vrRadioButton"] { + color: #49c944; } + section.tool-wrapper div#controlls-container label[for="vcCheckbox"], section.tool-wrapper div#controlls-container label[for="vcRadioButton"] { + color: #e1a600; } + section.tool-wrapper div#controlls-container label[for="vlCheckbox"], section.tool-wrapper div#controlls-container label[for="vlRadioButton"] { + color: #a26784; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders { + border-bottom: 1px solid #021014; + -webkit-box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + box-shadow: compact(0 1px 0 #083e4b, false, false, false, false, false, false, false, false); + margin-bottom: 22.652px; + padding: 0 0 22.652px; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders select#musicTypeSelect { + font: 16px "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-bottom: 0; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.top-sliders p { + font-weight: bold; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin: 0 11.326px 22.652px 0; + text-shadow: 0 -1px 0 #021014; + -webkit-font-smoothing: antialiased; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.slider-label { + font-weight: bold; + margin-bottom: 11.326px; + text-shadow: 0 -1px 0 #021014; + -webkit-font-smoothing: antialiased; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.slider { + margin-bottom: 22.652px; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.slider.ui-slider-horizontal { + background: #00232c; + border: 1px solid #000b0d; + -webkit-box-shadow: compact(none, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(none, false, false, false, false, false, false, false, false); + box-shadow: compact(none, false, false, false, false, false, false, false, false); + height: 0.4em; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.slider .ui-slider-handle { + background: #637c84 url("../images/amplifier-slider-handle.png") center no-repeat; + border: 1px solid #000b0d; + -webkit-box-shadow: compact(inset 0 1px 0 #8ba1a8, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #8ba1a8, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #8ba1a8, false, false, false, false, false, false, false, false); + margin-top: -0.3em; } + section.tool-wrapper div#controlls-container div.schematic-sliders div.slider .ui-slider-handle:hover, section.tool-wrapper div#controlls-container div.schematic-sliders div.slider .ui-slider-handle:active { + background-color: #6e8992; } + +section.problem-set, div.course-wrapper section.course-content section.problems-wrapper, section.problems-wrapper { + position: relative; } + section.problem-set h2, div.course-wrapper section.course-content section.problems-wrapper h2, section.problems-wrapper h2 { + margin-top: 0; + margin-bottom: 15px; + width: 20.109%; + padding-right: 2.717%; + border-right: 1px dashed #ddd; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; + vertical-align: top; } + section.problem-set h2.problem-header section.staff, div.course-wrapper section.course-content section.problems-wrapper h2.problem-header section.staff, section.problems-wrapper h2.problem-header section.staff { + margin-top: 30px; + font-size: 80%; } + @media screen and (max-width:1120px) { + section.problem-set h2, div.course-wrapper section.course-content section.problems-wrapper h2, section.problems-wrapper h2 { + display: block; + width: auto; + border-right: 0; } } + @media print { + section.problem-set h2, div.course-wrapper section.course-content section.problems-wrapper h2, section.problems-wrapper h2 { + display: block; + width: auto; + border-right: 0; } } + section.problem-set section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, section.problems-wrapper section.problem { + display: table-cell; + width: 77.174%; + padding-left: 2.717%; } + @media screen and (max-width:1120px) { + section.problem-set section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, section.problems-wrapper section.problem { + display: block; + width: auto; + padding: 0; } } + @media print { + section.problem-set section.problem, div.course-wrapper section.course-content section.problems-wrapper section.problem, section.problems-wrapper section.problem { + display: block; + width: auto; + padding: 0; } + section.problem-set section.problem canvas, div.course-wrapper section.course-content section.problems-wrapper section.problem canvas, section.problems-wrapper section.problem canvas, section.problem-set section.problem img, div.course-wrapper section.course-content section.problems-wrapper section.problem img, section.problems-wrapper section.problem img { + page-break-inside: avoid; } } + section.problem-set section.problem div p.status, div.course-wrapper section.course-content section.problems-wrapper section.problem div p.status, section.problems-wrapper section.problem div p.status { + text-indent: -9999px; + margin: -1px 0 0 10px; } + section.problem-set section.problem div.unanswered p.status, div.course-wrapper section.course-content section.problems-wrapper section.problem div.unanswered p.status, section.problems-wrapper section.problem div.unanswered p.status { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/unanswered-icon.png") center center no-repeat; + height: 14px; + width: 14px; } + section.problem-set section.problem div.correct p.status, div.course-wrapper section.course-content section.problems-wrapper section.problem div.correct p.status, section.problems-wrapper section.problem div.correct p.status, section.problem-set section.problem div.ui-icon-check p.status, div.course-wrapper section.course-content section.problems-wrapper section.problem div.ui-icon-check p.status, section.problems-wrapper section.problem div.ui-icon-check p.status { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/correct-icon.png") center center no-repeat; + height: 20px; + width: 25px; } + section.problem-set section.problem div.correct input, div.course-wrapper section.course-content section.problems-wrapper section.problem div.correct input, section.problems-wrapper section.problem div.correct input, section.problem-set section.problem div.ui-icon-check input, div.course-wrapper section.course-content section.problems-wrapper section.problem div.ui-icon-check input, section.problems-wrapper section.problem div.ui-icon-check input { + border-color: green; } + section.problem-set section.problem div.incorrect p.status, div.course-wrapper section.course-content section.problems-wrapper section.problem div.incorrect p.status, section.problems-wrapper section.problem div.incorrect p.status, section.problem-set section.problem div.ui-icon-close p.status, div.course-wrapper section.course-content section.problems-wrapper section.problem div.ui-icon-close p.status, section.problems-wrapper section.problem div.ui-icon-close p.status { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/incorrect-icon.png") center center no-repeat; + height: 20px; + width: 20px; + text-indent: -9999px; } + section.problem-set section.problem div.incorrect input, div.course-wrapper section.course-content section.problems-wrapper section.problem div.incorrect input, section.problems-wrapper section.problem div.incorrect input, section.problem-set section.problem div.ui-icon-close input, div.course-wrapper section.course-content section.problems-wrapper section.problem div.ui-icon-close input, section.problems-wrapper section.problem div.ui-icon-close input { + border-color: red; } + section.problem-set section.problem div > span, div.course-wrapper section.course-content section.problems-wrapper section.problem div > span, section.problems-wrapper section.problem div > span { + display: block; + margin-bottom: 11.326px; } + section.problem-set section.problem div p.answer, div.course-wrapper section.course-content section.problems-wrapper section.problem div p.answer, section.problems-wrapper section.problem div p.answer { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-bottom: 0; + margin-left: 10px; } + section.problem-set section.problem div p.answer:before, div.course-wrapper section.course-content section.problems-wrapper section.problem div p.answer:before, section.problems-wrapper section.problem div p.answer:before { + content: "Answer: "; + font-weight: bold; + display: inline; } + section.problem-set section.problem div p.answer:empty:before, div.course-wrapper section.course-content section.problems-wrapper section.problem div p.answer:empty:before, section.problems-wrapper section.problem div p.answer:empty:before { + display: none; } + section.problem-set section.problem div div.equation, div.course-wrapper section.course-content section.problems-wrapper section.problem div div.equation, section.problems-wrapper section.problem div div.equation { + clear: both; + padding: 6px; + background: #eee; } + section.problem-set section.problem div div.equation span, div.course-wrapper section.course-content section.problems-wrapper section.problem div div.equation span, section.problems-wrapper section.problem div div.equation span { + margin-bottom: 0; } + section.problem-set section.problem div span.unanswered, div.course-wrapper section.course-content section.problems-wrapper section.problem div span.unanswered, section.problems-wrapper section.problem div span.unanswered, section.problem-set section.problem div span.ui-icon-bullet, div.course-wrapper section.course-content section.problems-wrapper section.problem div span.ui-icon-bullet, section.problems-wrapper section.problem div span.ui-icon-bullet { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/unanswered-icon.png") center center no-repeat; + height: 14px; + position: relative; + top: 4px; + width: 14px; } + section.problem-set section.problem div span.correct, div.course-wrapper section.course-content section.problems-wrapper section.problem div span.correct, section.problems-wrapper section.problem div span.correct, section.problem-set section.problem div span.ui-icon-check, div.course-wrapper section.course-content section.problems-wrapper section.problem div span.ui-icon-check, section.problems-wrapper section.problem div span.ui-icon-check { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/correct-icon.png") center center no-repeat; + height: 20px; + position: relative; + top: 6px; + width: 25px; } + section.problem-set section.problem div span.incorrect, div.course-wrapper section.course-content section.problems-wrapper section.problem div span.incorrect, section.problems-wrapper section.problem div span.incorrect, section.problem-set section.problem div span.ui-icon-close, div.course-wrapper section.course-content section.problems-wrapper section.problem div span.ui-icon-close, section.problems-wrapper section.problem div span.ui-icon-close { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + background: url("../images/incorrect-icon.png") center center no-repeat; + height: 20px; + width: 20px; + position: relative; + top: 6px; } + section.problem-set section.problem ul, div.course-wrapper section.course-content section.problems-wrapper section.problem ul, section.problems-wrapper section.problem ul { + list-style: disc outside none; + margin-bottom: 22.652px; + margin-left: .75em; + margin-left: .75rem; } + section.problem-set section.problem ol, div.course-wrapper section.course-content section.problems-wrapper section.problem ol, section.problems-wrapper section.problem ol { + list-style: decimal outside none; + margin-bottom: 22.652px; + margin-left: .75em; + margin-left: .75rem; } + section.problem-set section.problem dl, div.course-wrapper section.course-content section.problems-wrapper section.problem dl, section.problems-wrapper section.problem dl { + line-height: 1.4em; } + section.problem-set section.problem dl dt, div.course-wrapper section.course-content section.problems-wrapper section.problem dl dt, section.problems-wrapper section.problem dl dt { + font-weight: bold; } + section.problem-set section.problem dl dd, div.course-wrapper section.course-content section.problems-wrapper section.problem dl dd, section.problems-wrapper section.problem dl dd { + margin-bottom: 0; } + section.problem-set section.problem dd, div.course-wrapper section.course-content section.problems-wrapper section.problem dd, section.problems-wrapper section.problem dd { + margin-left: .5em; + margin-left: .5rem; } + section.problem-set section.problem li, div.course-wrapper section.course-content section.problems-wrapper section.problem li, section.problems-wrapper section.problem li { + line-height: 1.4em; + margin-bottom: 11.326px; } + section.problem-set section.problem li:last-child, div.course-wrapper section.course-content section.problems-wrapper section.problem li:last-child, section.problems-wrapper section.problem li:last-child { + margin-bottom: 0; } + section.problem-set section.problem p, div.course-wrapper section.course-content section.problems-wrapper section.problem p, section.problems-wrapper section.problem p { + margin-bottom: 22.652px; } + section.problem-set section.problem table, div.course-wrapper section.course-content section.problems-wrapper section.problem table, section.problems-wrapper section.problem table { + margin-bottom: 22.652px; + width: 100%; + border-collapse: collapse; } + section.problem-set section.problem table th, div.course-wrapper section.course-content section.problems-wrapper section.problem table th, section.problems-wrapper section.problem table th { + font-weight: bold; + text-align: left; } + section.problem-set section.problem table caption, div.course-wrapper section.course-content section.problems-wrapper section.problem table caption, section.problems-wrapper section.problem table caption, section.problem-set section.problem table th, div.course-wrapper section.course-content section.problems-wrapper section.problem table th, section.problems-wrapper section.problem table th, section.problem-set section.problem table td, div.course-wrapper section.course-content section.problems-wrapper section.problem table td, section.problems-wrapper section.problem table td { + padding: .25em .75em .25em 0; + padding: .25rem .75rem .25rem 0; } + section.problem-set section.problem table caption, div.course-wrapper section.course-content section.problems-wrapper section.problem table caption, section.problems-wrapper section.problem table caption { + background: #f1f1f1; + margin-bottom: .75em; + margin-bottom: .75rem; + padding: .75em 0; + padding: .75rem 0; } + section.problem-set section.problem table tr, div.course-wrapper section.course-content section.problems-wrapper section.problem table tr, section.problems-wrapper section.problem table tr, section.problem-set section.problem table td, div.course-wrapper section.course-content section.problems-wrapper section.problem table td, section.problems-wrapper section.problem table td, section.problem-set section.problem table th, div.course-wrapper section.course-content section.problems-wrapper section.problem table th, section.problems-wrapper section.problem table th { + vertical-align: middle; } + section.problem-set section.problem hr, div.course-wrapper section.course-content section.problems-wrapper section.problem hr, section.problems-wrapper section.problem hr { + background: #ddd; + border: none; + clear: both; + color: #ddd; + float: none; + height: 1px; + margin: 0 0 .75rem; + width: 100%; } + section.problem-set section.problem .hidden, div.course-wrapper section.course-content section.problems-wrapper section.problem .hidden, section.problems-wrapper section.problem .hidden { + display: none; + visibility: hidden; } + section.problem-set section.problem input[type="email"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="email"], section.problems-wrapper section.problem input[type="email"], section.problem-set section.problem input[type="number"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="number"], section.problems-wrapper section.problem input[type="number"], section.problem-set section.problem input[type="password"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="password"], section.problems-wrapper section.problem input[type="password"], section.problem-set section.problem input[type="search"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="search"], section.problems-wrapper section.problem input[type="search"], section.problem-set section.problem input[type="tel"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="tel"], section.problems-wrapper section.problem input[type="tel"], section.problem-set section.problem input[type="text"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="text"], section.problems-wrapper section.problem input[type="text"], section.problem-set section.problem input[type="url"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="url"], section.problems-wrapper section.problem input[type="url"], section.problem-set section.problem input[type="color"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="color"], section.problems-wrapper section.problem input[type="color"], section.problem-set section.problem input[type="date"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="date"], section.problems-wrapper section.problem input[type="date"], section.problem-set section.problem input[type="datetime"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="datetime"], section.problems-wrapper section.problem input[type="datetime"], section.problem-set section.problem input[type="datetime-local"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="datetime-local"], section.problems-wrapper section.problem input[type="datetime-local"], section.problem-set section.problem input[type="month"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="month"], section.problems-wrapper section.problem input[type="month"], section.problem-set section.problem input[type="time"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="time"], section.problems-wrapper section.problem input[type="time"], section.problem-set section.problem input[type="week"], div.course-wrapper section.course-content section.problems-wrapper section.problem input[type="week"], section.problems-wrapper section.problem input[type="week"] { + display: inline; + width: auto; } + section.problem-set section.problem center, div.course-wrapper section.course-content section.problems-wrapper section.problem center, section.problems-wrapper section.problem center { + display: block; + margin: 22.652px 0; + border: 1px solid #ccc; + padding: 22.652px; } + section.problem-set section.action, div.course-wrapper section.course-content section.problems-wrapper section.action, section.problems-wrapper section.action { + margin-top: 11.326px; } + section.problem-set section.action input[type="button"], div.course-wrapper section.course-content section.problems-wrapper section.action input[type="button"], section.problems-wrapper section.action input[type="button"] { + padding: 9.061px 22.652px; + text-shadow: 0 -1px 0 #666666; } + +section.problems-wrapper { + display: table; + width: 100%; } + @media screen and (max-width:1120px) { + section.problems-wrapper { + display: block; + width: auto; } } diff --git a/lms/static/css/course/old/marketing-ie.css b/lms/static/css/course/old/marketing-ie.css new file mode 100644 index 0000000000..18d829fdeb --- /dev/null +++ b/lms/static/css/course/old/marketing-ie.css @@ -0,0 +1,10 @@ +body { + margin: 0; + padding: 0; } + +.wrapper, .subpage, section.copyright, section.tos, section.privacy-policy, section.honor-code, header.announcement div, section.index-content, footer { + margin: 0; + overflow: hidden; } + +div#enroll form { + display: none; } diff --git a/lms/static/css/course/old/marketing.css b/lms/static/css/course/old/marketing.css new file mode 100644 index 0000000000..5e2e01d8d3 --- /dev/null +++ b/lms/static/css/course/old/marketing.css @@ -0,0 +1,1017 @@ +/* +html5doctor.com Reset Stylesheet +v1.6.1 +Last Updated: 2010-09-17 +Author: Richard Clark - http://richclarkdesign.com +Twitter: @rich_clark +*/ +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, 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; + font-size: 100%; + vertical-align: baseline; + background: transparent; } + +body { + line-height: 1; } + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; } + +nav ul { + list-style: none; } + +blockquote, q { + quotes: none; } + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; } + +a { + margin: 0; + padding: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; } + +/* change colours to suit your needs */ +ins { + background-color: #ff9; + color: #000; + text-decoration: none; } + +/* change colours to suit your needs */ +mark { + background-color: #ff9; + color: #000; + font-style: italic; + font-weight: bold; } + +del { + text-decoration: line-through; } + +abbr[title], dfn[title] { + border-bottom: 1px dotted; + cursor: help; } + +table { + border-collapse: collapse; + border-spacing: 0; } + +/* change border colour to suit your needs */ +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #cccccc; + margin: 1em 0; + padding: 0; } + +input, select { + vertical-align: middle; } + +/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 25, 2012 05:06:34 PM America/New_York */ +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-Regular-webfont.eot"); + src: url("../fonts/OpenSans-Regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Regular-webfont.woff") format("woff"), url("../fonts/OpenSans-Regular-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular") format("svg"); + font-weight: 600; + font-style: normal; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-Italic-webfont.eot"); + src: url("../fonts/OpenSans-Italic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Italic-webfont.woff") format("woff"), url("../fonts/OpenSans-Italic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic") format("svg"); + font-weight: 400; + font-style: italic; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-Bold-webfont.eot"); + src: url("../fonts/OpenSans-Bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Bold-webfont.woff") format("woff"), url("../fonts/OpenSans-Bold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Bold-webfont.svg#OpenSansBold") format("svg"); + font-weight: 700; + font-style: normal; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-BoldItalic-webfont.eot"); + src: url("../fonts/OpenSans-BoldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-BoldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-BoldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic") format("svg"); + font-weight: 700; + font-style: italic; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-ExtraBold-webfont.eot"); + src: url("../fonts/OpenSans-ExtraBold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-ExtraBold-webfont.woff") format("woff"), url("../fonts/OpenSans-ExtraBold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-ExtraBold-webfont.svg#OpenSansExtrabold") format("svg"); + font-weight: 800; + font-style: normal; } + +@font-face { + font-family: 'Open Sans'; + src: url("../fonts/OpenSans-ExtraBoldItalic-webfont.eot"); + src: url("../fonts/OpenSans-ExtraBoldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.svg#OpenSansExtraboldItalic") format("svg"); + font-weight: 800; + font-style: italic; } + +.wrapper, .subpage, section.copyright, section.tos, section.privacy-policy, section.honor-code, header.announcement div, footer, section.index-content { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 0 auto; + max-width: 1400px; + padding: 25.888px; + width: 100%; } + +.subpage > div, section.copyright > div, section.tos > div, section.privacy-policy > div, section.honor-code > div { + padding-left: 34.171%; } + @media screen and (max-width: 940px) { + .subpage > div, section.copyright > div, section.tos > div, section.privacy-policy > div, section.honor-code > div { + padding-left: 0; } } + .subpage > div p, section.copyright > div p, section.tos > div p, section.privacy-policy > div p, section.honor-code > div p { + margin-bottom: 25.888px; + line-height: 25.888px; } + .subpage > div h1, section.copyright > div h1, section.tos > div h1, section.privacy-policy > div h1, section.honor-code > div h1 { + margin-bottom: 12.944px; } + .subpage > div h2, section.copyright > div h2, section.tos > div h2, section.privacy-policy > div h2, section.honor-code > div h2 { + font: 18px "Open Sans", Helvetica, Arial, sans-serif; + color: #000; + margin-bottom: 12.944px; } + .subpage > div ul, section.copyright > div ul, section.tos > div ul, section.privacy-policy > div ul, section.honor-code > div ul { + list-style: disc outside none; } + .subpage > div ul li, section.copyright > div ul li, section.tos > div ul li, section.privacy-policy > div ul li, section.honor-code > div ul li { + list-style: disc outside none; + line-height: 25.888px; } + .subpage > div dl, section.copyright > div dl, section.tos > div dl, section.privacy-policy > div dl, section.honor-code > div dl { + margin-bottom: 25.888px; } + .subpage > div dl dd, section.copyright > div dl dd, section.tos > div dl dd, section.privacy-policy > div dl dd, section.honor-code > div dl dd { + margin-bottom: 12.944px; } + +.clearfix:after, .subpage:after, section.copyright:after, section.tos:after, section.privacy-policy:after, section.honor-code:after, header.announcement div section:after, footer:after, section.index-content:after, section.index-content section:after, section.index-content section.about section:after, div.leanModal_box#enroll ol:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; } + +.button, header.announcement div section.course section a, section.index-content section.course a, section.index-content section.staff a, section.index-content section.about-course section.cta a.enroll { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); + background-color: #993333; + border: 1px solid #732626; + color: #fff; + margin: 25.888px 0 12.944px; + padding: 6.472px 12.944px; + text-decoration: none; + font-style: normal; + -webkit-box-shadow: compact(inset 0 1px 0 #b83d3d, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #b83d3d, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #b83d3d, false, false, false, false, false, false, false, false); + -webkit-font-smoothing: antialiased; } + .button:hover, header.announcement div section.course section a:hover, section.index-content section.course a:hover, section.index-content section.staff a:hover, section.index-content section.about-course section.cta a.enroll:hover { + background-color: #732626; + border-color: #4d1919; } + .button span, header.announcement div section.course section a span, section.index-content section.course a span, section.index-content section.staff a span, section.index-content section.about-course section.cta a.enroll span { + font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif; + font-style: italic; } + +p.ie-warning { + display: block !important; + line-height: 1.3em; + background: yellow; + margin-bottom: 25.888px; + padding: 25.888px; } + +body { + background-color: #fff; + color: #444; + font: 16px Georgia, serif; } + body :focus { + outline-color: #ccc; } + body h1 { + font: 800 24px "Open Sans", Helvetica, Arial, sans-serif; } + body li { + margin-bottom: 25.888px; } + body em { + font-style: italic; } + body a { + color: #993333; + font-style: italic; + text-decoration: none; } + body a:hover, body a:focus { + color: #732626; } + body input[type="email"], body input[type="number"], body input[type="password"], body input[type="search"], body input[type="tel"], body input[type="text"], body input[type="url"], body input[type="color"], body input[type="date"], body input[type="datetime"], body input[type="datetime-local"], body input[type="month"], body input[type="time"], body input[type="week"], body textarea { + -webkit-box-shadow: compact(0 -1px 0 white, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 -1px 0 white, false, false, false, false, false, false, false, false); + box-shadow: compact(0 -1px 0 white, false, false, false, false, false, false, false, false); + background-color: #eeeeee; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#eeeeee, white, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#eeeeee, white, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#eeeeee, white, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#eeeeee, white, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#eeeeee, white, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#eeeeee, white, false, false, false, false, false, false, false, false)); + border: 1px solid #999; + font: 16px Georgia, serif; + padding: 4px; + width: 100%; } + body input[type="email"]:focus, body input[type="number"]:focus, body input[type="password"]:focus, body input[type="search"]:focus, body input[type="tel"]:focus, body input[type="text"]:focus, body input[type="url"]:focus, body input[type="color"]:focus, body input[type="date"]:focus, body input[type="datetime"]:focus, body input[type="datetime-local"]:focus, body input[type="month"]:focus, body input[type="time"]:focus, body input[type="week"]:focus, body textarea:focus { + border-color: #993333; } + +header.announcement { + -webkit-background-size: compact(cover, false, false, false, false, false, false, false, false); + -moz-background-size: compact(cover, false, false, false, false, false, false, false, false); + -ms-background-size: compact(cover, false, false, false, false, false, false, false, false); + -o-background-size: compact(cover, false, false, false, false, false, false, false, false); + background-size: compact(cover, false, false, false, false, false, false, false, false); + background: #333; + border-bottom: 1px solid #000; + color: #fff; + -webkit-font-smoothing: antialiased; } + header.announcement.home { + background: #e3e3e3 url("../images/marketing/shot-5-medium.jpg"); } + @media screen and (min-width: 1200px) { + header.announcement.home { + background: #e3e3e3 url("../images/marketing/shot-5-large.jpg"); } } + header.announcement.home div { + padding: 258.88px 25.888px 77.664px; } + @media screen and (max-width:780px) { + header.announcement.home div { + padding: 64.72px 25.888px 51.776px; } } + header.announcement.home div nav h1 { + margin-right: 0; } + header.announcement.home div nav a.login { + display: none; } + header.announcement.course { + background: #e3e3e3 url("../images/marketing/course-bg-small.jpg"); } + @media screen and (min-width: 1200px) { + header.announcement.course { + background: #e3e3e3 url("../images/marketing/course-bg-large.jpg"); } } + @media screen and (max-width: 1199px) and (min-width: 700px) { + header.announcement.course { + background: #e3e3e3 url("../images/marketing/course-bg-medium.jpg"); } } + header.announcement.course div { + padding: 103.552px 25.888px 51.776px; } + @media screen and (max-width:780px) { + header.announcement.course div { + padding: 64.72px 25.888px 51.776px; } } + header.announcement div { + position: relative; } + header.announcement div nav { + position: absolute; + top: 0; + right: 25.888px; + -webkit-border-radius: 0 0 3px 3px; + -moz-border-radius: 0 0 3px 3px; + -ms-border-radius: 0 0 3px 3px; + -o-border-radius: 0 0 3px 3px; + border-radius: 0 0 3px 3px; + background: #333; + background: rgba(0, 0, 0, 0.7); + padding: 12.944px 25.888px; } + header.announcement div nav h1 { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-right: 12.944px; } + header.announcement div nav h1 a { + font: italic 800 18px "Open Sans", Helvetica, Arial, sans-serif; + color: #fff; + text-decoration: none; } + header.announcement div nav h1 a:hover, header.announcement div nav h1 a:focus { + color: #999; } + header.announcement div nav a.login { + text-decoration: none; + color: #fff; + font-size: 12px; + font-style: normal; + font-family: "Open Sans", Helvetica, Arial, sans-serif; } + header.announcement div nav a.login:hover, header.announcement div nav a.login:focus { + color: #999; } + header.announcement div section { + background: #993333; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-left: 34.171%; + padding: 25.888px 38.832px; } + @media screen and (max-width: 780px) { + header.announcement div section { + margin-left: 0; } } + header.announcement div section h1 { + font-family: "Open Sans"; + font-size: 30px; + font-weight: 800; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + line-height: 1.2em; + margin: 0 25.888px 0 0; } + header.announcement div section h2 { + font-family: "Open Sans"; + font-size: 24px; + font-weight: 400; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + line-height: 1.2em; } + header.announcement div section.course section { + float: left; + margin-left: 0; + margin-right: 3.817%; + padding: 0; + width: 48.092%; } + @media screen and (max-width: 780px) { + header.announcement div section.course section { + float: none; + width: 100%; + margin-right: 0; } } + header.announcement div section.course section a { + background-color: #4d1919; + border-color: #260d0d; + -webkit-box-shadow: compact(inset 0 1px 0 #732626, 0 1px 0 #ac3939, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 #732626, 0 1px 0 #ac3939, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 #732626, 0 1px 0 #ac3939, false, false, false, false, false, false, false); + display: block; + padding: 12.944px 25.888px; + text-align: center; } + header.announcement div section.course section a:hover { + background-color: #732626; + border-color: #4d1919; } + header.announcement div section.course p { + width: 48.092%; + line-height: 25.888px; + float: left; } + @media screen and (max-width: 780px) { + header.announcement div section.course p { + float: none; + width: 100%; } } + +footer { + padding-top: 0; } + footer div.footer-wrapper { + border-top: 1px solid #e5e5e5; + padding: 25.888px 0; + background: url("../images/marketing/mit-logo.png") right center no-repeat; } + @media screen and (max-width: 780px) { + footer div.footer-wrapper { + background-position: left bottom; + padding-bottom: 77.664px; } } + footer div.footer-wrapper a { + color: #888; + text-decoration: none; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + footer div.footer-wrapper a:hover, footer div.footer-wrapper a:focus { + color: #666; } + footer div.footer-wrapper p { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-right: 25.888px; } + footer div.footer-wrapper ul { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + @media screen and (max-width: 780px) { + footer div.footer-wrapper ul { + margin-top: 25.888px; } } + footer div.footer-wrapper ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-bottom: 0; } + footer div.footer-wrapper ul li:after { + content: ' |'; + display: inline; + color: #ccc; } + footer div.footer-wrapper ul li:last-child:after { + content: none; } + footer div.footer-wrapper ul.social { + float: right; + margin-right: 60px; + position: relative; + top: -5px; } + @media screen and (max-width: 780px) { + footer div.footer-wrapper ul.social { + float: none; } } + footer div.footer-wrapper ul.social li { + float: left; + margin-right: 12.944px; } + footer div.footer-wrapper ul.social li:after { + content: none; + display: none; } + footer div.footer-wrapper ul.social li a { + display: block; + height: 29px; + width: 28px; + text-indent: -9999px; } + footer div.footer-wrapper ul.social li a:hover { + opacity: .8; } + footer div.footer-wrapper ul.social li.twitter a { + background: url("../images/marketing/twitter.png") 0 0 no-repeat; } + footer div.footer-wrapper ul.social li.facebook a { + background: url("../images/marketing/facebook.png") 0 0 no-repeat; } + footer div.footer-wrapper ul.social li.linkedin a { + background: url("../images/marketing/linkedin.png") 0 0 no-repeat; } + +section.index-content section { + float: left; } + @media screen and (max-width: 780px) { + section.index-content section { + float: none; + width: auto; + margin-right: 0; } } + section.index-content section h1 { + font-size: 800 24px "Open Sans"; + margin-bottom: 25.888px; } + section.index-content section p { + line-height: 25.888px; + margin-bottom: 25.888px; } + section.index-content section ul { + margin: 0; } + section.index-content section.about { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-right: 1px solid #e5e5e5; + margin-right: 2.513%; + padding-right: 1.256%; + width: 65.829%; } + @media screen and (max-width: 780px) { + section.index-content section.about { + width: 100%; + border-right: 0; + margin-right: 0; + padding-right: 0; } } + section.index-content section.about section { + margin-bottom: 25.888px; } + section.index-content section.about section p { + width: 48.092%; + float: left; } + @media screen and (max-width: 780px) { + section.index-content section.about section p { + float: none; + width: auto; } } + section.index-content section.about section p:nth-child(odd) { + margin-right: 3.817%; } + @media screen and (max-width: 780px) { + section.index-content section.about section p:nth-child(odd) { + margin-right: 0; } } + section.index-content section.about section.intro section { + margin-bottom: 0; } + section.index-content section.about section.intro section.intro-text { + margin-right: 3.817%; + width: 48.092%; } + @media screen and (max-width: 780px) { + section.index-content section.about section.intro section.intro-text { + margin-right: 0; + width: auto; } } + section.index-content section.about section.intro section.intro-text p { + margin-right: 0; + width: auto; + float: none; } + section.index-content section.about section.intro section.intro-video { + width: 48.092%; } + @media screen and (max-width: 780px) { + section.index-content section.about section.intro section.intro-video { + width: auto; } } + section.index-content section.about section.intro section.intro-video a { + display: block; + width: 100%; } + section.index-content section.about section.intro section.intro-video a img { + width: 100%; } + section.index-content section.about section.intro section.intro-video a span { + display: none; } + section.index-content section.about section.features { + border-top: 1px solid #E5E5E5; + padding-top: 25.888px; + margin-bottom: 0; } + section.index-content section.about section.features h2 { + text-transform: uppercase; + letter-spacing: 1px; + color: #888; + margin-bottom: 25.888px; + font-weight: normal; + font-size: 14px; } + section.index-content section.about section.features h2 span { + text-transform: none; } + section.index-content section.about section.features p { + width: auto; + clear: both; } + section.index-content section.about section.features p strong { + font-family: "Open sans"; + font-weight: 800; } + section.index-content section.about section.features p a { + color: #993333; + text-decoration: none; + -webkit-transition-property: compact(all, false, false, false, false, false, false, false, false); + -moz-transition-property: compact(all, false, false, false, false, false, false, false, false); + -ms-transition-property: compact(all, false, false, false, false, false, false, false, false); + -o-transition-property: compact(all, false, false, false, false, false, false, false, false); + transition-property: compact(all, false, false, false, false, false, false, false, false); + -webkit-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -moz-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -ms-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -o-transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + transition-duration: compact(0.15s, false, false, false, false, false, false, false, false); + -webkit-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -moz-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -ms-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -o-transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + transition-timing-function: compact(ease-out, false, false, false, false, false, false, false, false); + -webkit-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -moz-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -ms-transition-delay: compact(0, false, false, false, false, false, false, false, false); + -o-transition-delay: compact(0, false, false, false, false, false, false, false, false); + transition-delay: compact(0, false, false, false, false, false, false, false, false); } + section.index-content section.about section.features p a:hover, section.index-content section.about section.features p a:focus { + color: #602020; } + section.index-content section.about section.features ul { + margin-bottom: 0; } + section.index-content section.about section.features ul li { + line-height: 25.888px; + width: 48.092%; + float: left; + margin-bottom: 12.944px; } + @media screen and (max-width: 780px) { + section.index-content section.about section.features ul li { + width: auto; + float: none; } } + section.index-content section.about section.features ul li:nth-child(odd) { + margin-right: 3.817%; } + @media screen and (max-width: 780px) { + section.index-content section.about section.features ul li:nth-child(odd) { + margin-right: 0; } } + section.index-content section.course, section.index-content section.staff { + width: 31.658%; } + @media screen and (max-width: 780px) { + section.index-content section.course, section.index-content section.staff { + width: auto; } } + section.index-content section.course h1, section.index-content section.staff h1 { + color: #888; + font: normal 16px Georgia, serif; + font-size: 14px; + letter-spacing: 1px; + margin-bottom: 25.888px; + text-transform: uppercase; } + section.index-content section.course h2, section.index-content section.staff h2 { + font: 800 24px "Open Sans", Helvetica, Arial, sans-serif; } + section.index-content section.course h3, section.index-content section.staff h3 { + font: 400 18px "Open Sans", Helvetica, Arial, sans-serif; } + section.index-content section.course a span.arrow, section.index-content section.staff a span.arrow { + color: rgba(255, 255, 255, 0.6); + font-style: normal; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + padding-left: 10px; } + section.index-content section.course ul, section.index-content section.staff ul { + list-style: none; } + section.index-content section.course ul li img, section.index-content section.staff ul li img { + float: left; + margin-right: 12.944px; } + section.index-content section.course h2 { + padding-top: 129.44px; + background: url("../images/marketing/circuits-bg.jpg") 0 0 no-repeat; + -webkit-background-size: compact(contain, false, false, false, false, false, false, false, false); + -moz-background-size: compact(contain, false, false, false, false, false, false, false, false); + -ms-background-size: compact(contain, false, false, false, false, false, false, false, false); + -o-background-size: compact(contain, false, false, false, false, false, false, false, false); + background-size: compact(contain, false, false, false, false, false, false, false, false); } + @media screen and (max-width: 998px) and (min-width: 781px) { + section.index-content section.course h2 { + background: url("../images/marketing/circuits-medium-bg.jpg") 0 0 no-repeat; } } + @media screen and (max-width: 780px) { + section.index-content section.course h2 { + padding-top: 129.44px; + background: url("../images/marketing/circuits-bg.jpg") 0 0 no-repeat; } } + @media screen and (min-width: 500px) and (max-width: 781px) { + section.index-content section.course h2 { + padding-top: 207.104px; } } + section.index-content section.course div.announcement p.announcement-button a { + margin-top: 0; } + section.index-content section.course div.announcement img { + max-width: 100%; + margin-bottom: 25.888px; } + section.index-content section.about-course { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-right: 1px solid #e5e5e5; + margin-right: 2.513%; + padding-right: 1.256%; + width: 65.829%; } + @media screen and (max-width: 780px) { + section.index-content section.about-course { + width: auto; + border-right: 0; + margin-right: 0; + padding-right: 0; } } + section.index-content section.about-course section { + width: 48.092%; } + @media screen and (max-width: 780px) { + section.index-content section.about-course section { + width: auto; } } + section.index-content section.about-course section.about-info { + margin-right: 3.817%; } + @media screen and (max-width: 780px) { + section.index-content section.about-course section.about-info { + margin-right: 0; } } + section.index-content section.about-course section.requirements { + clear: both; + width: 100%; + border-top: 1px solid #E5E5E5; + padding-top: 25.888px; + margin-bottom: 0; } + section.index-content section.about-course section.requirements p { + float: left; + width: 48.092%; + margin-right: 3.817%; } + @media screen and (max-width: 780px) { + section.index-content section.about-course section.requirements p { + margin-right: 0; + float: none; + width: auto; } } + section.index-content section.about-course section.requirements p:nth-child(odd) { + margin-right: 0; } + section.index-content section.about-course section.cta { + width: 100%; + text-align: center; } + section.index-content section.about-course section.cta a.enroll { + padding: 12.944px 51.776px; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + text-align: center; + font: 800 18px "Open Sans", Helvetica, Arial, sans-serif; } + section.index-content section.staff h1 { + margin-top: 25.888px; } + +#lean_overlay { + background: #000; + display: none; + height: 100%; + left: 0px; + position: fixed; + top: 0px; + width: 100%; + z-index: 100; } + +div.leanModal_box { + background: #fff; + border: none; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: compact(0 0 6px black, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(0 0 6px black, false, false, false, false, false, false, false, false); + box-shadow: compact(0 0 6px black, false, false, false, false, false, false, false, false); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: none; + padding: 51.776px; + text-align: left; } + div.leanModal_box a.modal_close { + color: #aaa; + display: block; + font-style: normal; + height: 14px; + position: absolute; + right: 12px; + top: 12px; + width: 14px; + z-index: 2; } + div.leanModal_box a.modal_close:hover { + color: #993333; + text-decoration: none; } + div.leanModal_box h1 { + border-bottom: 1px solid #eee; + font-size: 24px; + margin-bottom: 25.888px; + margin-top: 0; + padding-bottom: 25.888px; + text-align: left; } + div.leanModal_box#enroll { + max-width: 600px; } + div.leanModal_box#enroll ol { + padding-top: 25.888px; } + div.leanModal_box#enroll ol li.terms, div.leanModal_box#enroll ol li.honor-code { + float: none; + width: auto; } + div.leanModal_box#enroll ol li div.tip { + display: none; } + div.leanModal_box#enroll ol li:hover div.tip { + background: #333; + color: #fff; + display: block; + font-size: 16px; + line-height: 25.888px; + margin: 0 0 0 -10px; + padding: 10px; + position: absolute; + -webkit-font-smoothing: antialiased; + width: 500px; } + div.leanModal_box form { + text-align: left; } + div.leanModal_box form div#register_error, div.leanModal_box form div#login_error, div.leanModal_box form div#pwd_error { + background-color: #333333; + border: black; + color: #fff; + font-family: "Open sans"; + font-weight: bold; + letter-spacing: 1px; + margin: -25.888px -25.888px 25.888px; + padding: 12.944px; + text-shadow: 0 1px 0 #1a1a1a; + -webkit-font-smoothing: antialiased; } + div.leanModal_box form div#register_error:empty, div.leanModal_box form div#login_error:empty, div.leanModal_box form div#pwd_error:empty { + padding: 0; } + div.leanModal_box form ol { + list-style: none; + margin-bottom: 25.888px; } + div.leanModal_box form ol li { + margin-bottom: 12.944px; } + div.leanModal_box form ol li.terms, div.leanModal_box form ol li.remember { + border-top: 1px solid #eee; + clear: both; + float: none; + padding-top: 25.888px; + width: auto; } + div.leanModal_box form ol li.honor-code { + float: none; + width: auto; } + div.leanModal_box form ol li label { + display: block; + font-weight: bold; } + div.leanModal_box form ol li input[type="email"], div.leanModal_box form ol li input[type="number"], div.leanModal_box form ol li input[type="password"], div.leanModal_box form ol li input[type="search"], div.leanModal_box form ol li input[type="tel"], div.leanModal_box form ol li input[type="text"], div.leanModal_box form ol li input[type="url"], div.leanModal_box form ol li input[type="color"], div.leanModal_box form ol li input[type="date"], div.leanModal_box form ol li input[type="datetime"], div.leanModal_box form ol li input[type="datetime-local"], div.leanModal_box form ol li input[type="month"], div.leanModal_box form ol li input[type="time"], div.leanModal_box form ol li input[type="week"], div.leanModal_box form ol li textarea { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; } + div.leanModal_box form ol li input[type="checkbox"] { + margin-right: 10px; } + div.leanModal_box form ol li ul { + list-style: disc outside none; + margin: 12.944px 0 25.888px 25.888px; } + div.leanModal_box form ol li ul li { + color: #666; + float: none; + font-size: 14px; + list-style: disc outside none; + margin-bottom: 12.944px; } + div.leanModal_box form input[type="button"], div.leanModal_box form input[type="submit"] { + border: 1px solid #691b1b; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: compact(inset 0 1px 0 0 #bc5c5c, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #bc5c5c, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #bc5c5c, false, false, false, false, false, false, false, false); + color: white; + display: inline; + font-size: 11px; + font-weight: bold; + background-color: #993333; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#993333, #761e1e, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#993333, #761e1e, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#993333, #761e1e, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#993333, #761e1e, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#993333, #761e1e, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#993333, #761e1e, false, false, false, false, false, false, false, false)); + padding: 6px 18px 7px; + text-shadow: 0 1px 0 #5d1414; + -webkit-background-clip: padding-box; + font-size: 18px; + padding: 12.944px; } + div.leanModal_box form input[type="button"]:hover, div.leanModal_box form input[type="submit"]:hover { + -webkit-box-shadow: compact(inset 0 1px 0 0 #a44141, false, false, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 1px 0 0 #a44141, false, false, false, false, false, false, false, false); + box-shadow: compact(inset 0 1px 0 0 #a44141, false, false, false, false, false, false, false, false); + cursor: pointer; + background-color: #823030; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(100%, compact(#823030, #691c1c, false, false, false, false, false, false, false, false))); + background-image: -webkit-linear-gradient(top, compact(#823030, #691c1c, false, false, false, false, false, false, false, false)); + background-image: -moz-linear-gradient(top, compact(#823030, #691c1c, false, false, false, false, false, false, false, false)); + background-image: -ms-linear-gradient(top, compact(#823030, #691c1c, false, false, false, false, false, false, false, false)); + background-image: -o-linear-gradient(top, compact(#823030, #691c1c, false, false, false, false, false, false, false, false)); + background-image: linear-gradient(top, compact(#823030, #691c1c, false, false, false, false, false, false, false, false)); } + div.leanModal_box form input[type="button"]:active, div.leanModal_box form input[type="submit"]:active { + border: 1px solid #691b1b; + -webkit-box-shadow: compact(inset 0 0 8px 4px #5c1919, inset 0 0 8px 4px #5c1919, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); + -moz-box-shadow: compact(inset 0 0 8px 4px #5c1919, inset 0 0 8px 4px #5c1919, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); + box-shadow: compact(inset 0 0 8px 4px #5c1919, inset 0 0 8px 4px #5c1919, 0 1px 1px 0 #eeeeee, false, false, false, false, false, false); } + +div#login { + min-width: 400px; } + div#login header { + border-bottom: 1px solid #ddd; + margin-bottom: 25.888px; + padding-bottom: 25.888px; } + div#login header h1 { + border-bottom: 0; + padding-bottom: 0; + margin-bottom: 6.472px; } + div#login ol li { + float: none; + width: auto; } + +div.lost-password { + margin-top: 25.888px; + text-align: left; } + div.lost-password a { + color: #999; } + div.lost-password a:hover { + color: #444; } + +div#pwd_reset p { + margin-bottom: 25.888px; } +div#pwd_reset input[type="email"] { + margin-bottom: 25.888px; } + +div#apply_name_change, +div#change_email, +div#unenroll, +div#deactivate-account { + max-width: 700px; } + div#apply_name_change ul, + div#change_email ul, + div#unenroll ul, + div#deactivate-account ul { + list-style: none; } + div#apply_name_change ul li, + div#change_email ul li, + div#unenroll ul li, + div#deactivate-account ul li { + margin-bottom: 12.944px; } + div#apply_name_change ul li textarea, div#apply_name_change ul li input[type="email"], div#apply_name_change ul li input[type="number"], div#apply_name_change ul li input[type="password"], div#apply_name_change ul li input[type="search"], div#apply_name_change ul li input[type="tel"], div#apply_name_change ul li input[type="text"], div#apply_name_change ul li input[type="url"], div#apply_name_change ul li input[type="color"], div#apply_name_change ul li input[type="date"], div#apply_name_change ul li input[type="datetime"], div#apply_name_change ul li input[type="datetime-local"], div#apply_name_change ul li input[type="month"], div#apply_name_change ul li input[type="time"], div#apply_name_change ul li input[type="week"], + div#change_email ul li textarea, + div#change_email ul li input[type="email"], + div#change_email ul li input[type="number"], + div#change_email ul li input[type="password"], + div#change_email ul li input[type="search"], + div#change_email ul li input[type="tel"], + div#change_email ul li input[type="text"], + div#change_email ul li input[type="url"], + div#change_email ul li input[type="color"], + div#change_email ul li input[type="date"], + div#change_email ul li input[type="datetime"], + div#change_email ul li input[type="datetime-local"], + div#change_email ul li input[type="month"], + div#change_email ul li input[type="time"], + div#change_email ul li input[type="week"], + div#unenroll ul li textarea, + div#unenroll ul li input[type="email"], + div#unenroll ul li input[type="number"], + div#unenroll ul li input[type="password"], + div#unenroll ul li input[type="search"], + div#unenroll ul li input[type="tel"], + div#unenroll ul li input[type="text"], + div#unenroll ul li input[type="url"], + div#unenroll ul li input[type="color"], + div#unenroll ul li input[type="date"], + div#unenroll ul li input[type="datetime"], + div#unenroll ul li input[type="datetime-local"], + div#unenroll ul li input[type="month"], + div#unenroll ul li input[type="time"], + div#unenroll ul li input[type="week"], + div#deactivate-account ul li textarea, + div#deactivate-account ul li input[type="email"], + div#deactivate-account ul li input[type="number"], + div#deactivate-account ul li input[type="password"], + div#deactivate-account ul li input[type="search"], + div#deactivate-account ul li input[type="tel"], + div#deactivate-account ul li input[type="text"], + div#deactivate-account ul li input[type="url"], + div#deactivate-account ul li input[type="color"], + div#deactivate-account ul li input[type="date"], + div#deactivate-account ul li input[type="datetime"], + div#deactivate-account ul li input[type="datetime-local"], + div#deactivate-account ul li input[type="month"], + div#deactivate-account ul li input[type="time"], + div#deactivate-account ul li input[type="week"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: block; + width: 100%; } + div#apply_name_change ul li textarea, + div#change_email ul li textarea, + div#unenroll ul li textarea, + div#deactivate-account ul li textarea { + height: 60px; } + div#apply_name_change ul li input[type="submit"], + div#change_email ul li input[type="submit"], + div#unenroll ul li input[type="submit"], + div#deactivate-account ul li input[type="submit"] { + white-space: normal; } + +div#feedback_div form ol li { + float: none; + width: 100%; } + div#feedback_div form ol li textarea#feedback_message { + height: 100px; } diff --git a/lms/static/css/course/old/print.css b/lms/static/css/course/old/print.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index ae739c9589..0311d7c75d 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -6,7 +6,7 @@ $comment_info_size: 0.75em; $discussion_input_width: 60%; @mixin discussion-font { - font-family: "Comic Sans MS", cursive, sans-serif !important; + font-family: inherit; } @mixin discussion-clickable { diff --git a/lms/static/sass/_news.scss b/lms/static/sass/_news.scss index b8050f80c2..5c783faba1 100644 --- a/lms/static/sass/_news.scss +++ b/lms/static/sass/_news.scss @@ -1,5 +1,5 @@ @mixin news-font { - font-family: "Comic Sans MS", cursive, sans-serif !important; + font-family: inherit; } .notifications { diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html index db1f2e5153..62a34cb3d7 100644 --- a/lms/templates/discussion/index.html +++ b/lms/templates/discussion/index.html @@ -23,9 +23,8 @@ }); - + ## This must appear after all mathjax-config blocks, so it is after the imports from the other templates. + ## It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of MathJax extension libraries @@ -42,12 +41,12 @@

    Discussion Boards

    close + -
    diff --git a/lms/templates/discussion/thread.html b/lms/templates/discussion/thread.html index 5997b1b70c..9452b8a435 100644 --- a/lms/templates/discussion/thread.html +++ b/lms/templates/discussion/thread.html @@ -89,6 +89,7 @@ %>
    ${upvote} + ${content['votes']['point']} ${downvote}
    From 4535188099f8abf45fe7ac5a9639f3b2b7000542 Mon Sep 17 00:00:00 2001 From: Brittany Cheng Date: Fri, 27 Jul 2012 17:20:37 -0400 Subject: [PATCH 050/352] style changes --- lms/templates/discussion/inline.html | 2 +- lms/templates/discussion/search_bar.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lms/templates/discussion/inline.html b/lms/templates/discussion/inline.html index 8dbc50e337..17e7a5aa7b 100644 --- a/lms/templates/discussion/inline.html +++ b/lms/templates/discussion/inline.html @@ -7,7 +7,7 @@
    ${search_bar}
    - +
    New Post diff --git a/lms/templates/discussion/search_bar.html b/lms/templates/discussion/search_bar.html index bf3ec6a02f..1da5fc9fdb 100644 --- a/lms/templates/discussion/search_bar.html +++ b/lms/templates/discussion/search_bar.html @@ -6,8 +6,8 @@ def url_for_search(): %> - - - + Search + +
    From 0790a3add47db4f66766cad342a88614bd189f4d Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Fri, 27 Jul 2012 17:50:38 -0400 Subject: [PATCH 051/352] image uploading & render markdown --- common/static/js/vendor/Markdown.Editor.js | 35 +- common/static/js/vendor/customwmd.js | 3532 ----------------- .../static/js/vendor/jquery.ajaxfileupload.js | 206 + .../django_comment_client/base/urls.py | 1 + .../django_comment_client/base/views.py | 95 +- lms/envs/common.py | 1 + lms/envs/discussionsettings.py | 1 + lms/static/coffee/src/customwmd.coffee | 41 +- lms/static/coffee/src/discussion.coffee | 16 +- lms/templates/discussion/index.html | 1 + lms/templates/discussion/thread.html | 6 +- 11 files changed, 384 insertions(+), 3551 deletions(-) delete mode 100644 common/static/js/vendor/customwmd.js create mode 100644 common/static/js/vendor/jquery.ajaxfileupload.js create mode 100644 lms/envs/discussionsettings.py diff --git a/common/static/js/vendor/Markdown.Editor.js b/common/static/js/vendor/Markdown.Editor.js index 9959ade634..38d71c8ef7 100644 --- a/common/static/js/vendor/Markdown.Editor.js +++ b/common/static/js/vendor/Markdown.Editor.js @@ -28,7 +28,7 @@ // The text that appears on the upper part of the dialog box when // entering links. var linkDialogText = "

    Insert Hyperlink

    http://example.com/ \"optional title\"

    "; - var imageDialogText = "

    Insert Image

    http://example.com/images/diagram.jpg \"optional title\"

    Need free image hosting?

    "; + var imageDialogText = "

    Insert Image (upload file or type url)

    http://example.com/images/diagram.jpg \"optional title\"

    "; // The default text that appears in the dialog input box when entering // links. @@ -49,7 +49,7 @@ // - getConverter() returns the markdown converter object that was passed to the constructor // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op. // - refreshPreview() forces the preview to be updated. This method is only available after run() was called. - Markdown.Editor = function (markdownConverter, idPostfix, help) { + Markdown.Editor = function (markdownConverter, idPostfix, help, imageUploadHandler) { idPostfix = idPostfix || ""; @@ -88,7 +88,7 @@ } } - uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help); + uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help, imageUploadHandler); uiManager.setUndoRedoButtonStates(); var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; @@ -1010,7 +1010,7 @@ // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel. // It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel // was chosen). - ui.prompt = function (text, defaultInputText, callback) { + ui.prompt = function (text, defaultInputText, callback, imageUploadHandler) { // These variables need to be declared at this level since they are used // in multiple functions. @@ -1044,8 +1044,10 @@ else { // Fixes common pasting errors. text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://'); - if (!/^(?:https?|ftp):\/\//.test(text)) + // doesn't change url if started with '/' (local) + if (!/^(?:https?|ftp):\/\//.test(text) && text.charAt(0) != '/') { text = 'http://' + text; + } } dialog.parentNode.removeChild(dialog); @@ -1095,6 +1097,21 @@ style.marginLeft = style.marginRight = "auto"; form.appendChild(input); + // The choose file button if prompt type is 'image' + + if (imageUploadHandler) { + var chooseFile = doc.createElement("input"); + chooseFile.type = "file"; + chooseFile.name = "file-upload"; + chooseFile.id = "file-upload"; + chooseFile.onchange = function() { + imageUploadHandler(this, input); + }; + form.appendChild(doc.createElement("br")); + form.appendChild(chooseFile); + } + + // The ok button var okButton = doc.createElement("input"); okButton.type = "button"; @@ -1160,7 +1177,7 @@ }, 0); }; - function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions) { + function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions, imageUploadHandler) { var inputBox = panels.input, buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements. @@ -1419,7 +1436,7 @@ buttons.quote = makeButton("wmd-quote-button", "Blockquote
    Ctrl+Q", "-60px", bindCommand("doBlockquote")); buttons.code = makeButton("wmd-code-button", "Code Sample
     Ctrl+K", "-80px", bindCommand("doCode"));
                 buttons.image = makeButton("wmd-image-button", "Image  Ctrl+G", "-100px", bindCommand(function (chunk, postProcessing) {
    -                return this.doLinkOrImage(chunk, postProcessing, true);
    +                return this.doLinkOrImage(chunk, postProcessing, true, imageUploadHandler);
                 }));
                 makeSpacer(2);
                 buttons.olist = makeButton("wmd-olist-button", "Numbered List 
      Ctrl+O", "-120px", bindCommand(function (chunk, postProcessing) { @@ -1649,7 +1666,7 @@ }); } - commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) { + commandProto.doLinkOrImage = function (chunk, postProcessing, isImage, imageUploadHandler) { chunk.trimWhitespace(); chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/); @@ -1724,7 +1741,7 @@ if (isImage) { if (!this.hooks.insertImageDialog(linkEnteredCallback)) - ui.prompt(imageDialogText, imageDefaultText, linkEnteredCallback); + ui.prompt(imageDialogText, imageDefaultText, linkEnteredCallback, imageUploadHandler); } else { ui.prompt(linkDialogText, linkDefaultText, linkEnteredCallback); diff --git a/common/static/js/vendor/customwmd.js b/common/static/js/vendor/customwmd.js deleted file mode 100644 index ca3c48bc21..0000000000 --- a/common/static/js/vendor/customwmd.js +++ /dev/null @@ -1,3532 +0,0 @@ -var Attacklab = Attacklab || {}; - -/* begin showdown.js */ - -// -// showdown.js -- A javascript port of Markdown. -// -// Copyright (c) 2007 John Fraser. -// -// Original Markdown Copyright (c) 2004-2005 John Gruber -// -// -// Redistributable under a BSD-style open source license. -// See license.txt for more information. -// -// The full source distribution is at: -// -// A A L -// T C A -// T K B -// -// -// - -// -// Wherever possible, Showdown is a straight, line-by-line port -// of the Perl version of Markdown. -// -// This is not a normal parser design; it's basically just a -// series of string substitutions. It's hard to read and -// maintain this way, but keeping Showdown close to the original -// design makes it easier to port new features. -// -// More importantly, Showdown behaves like markdown.pl in most -// edge cases. So web applications can do client-side preview -// in Javascript, and then build identical HTML on the server. -// -// This port needs the new RegExp functionality of ECMA 262, -// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers -// should do fine. Even with the new regular expression features, -// We do a lot of work to emulate Perl's regex functionality. -// The tricky changes in this file mostly have the "attacklab:" -// label. Major or self-explanatory changes don't. -// -// Smart diff tools like Araxis Merge will be able to match up -// this file with markdown.pl in a useful way. A little tweaking -// helps: in a copy of markdown.pl, replace "#" with "//" and -// replace "$text" with "text". Be sure to ignore whitespace -// and line endings. -// - - -// -// Showdown usage: -// -// var text = "Markdown *rocks*."; -// -// var converter = new Attacklab.showdown.converter(); -// var html = converter.makeHtml(text); -// -// alert(html); -// -// Note: move the sample code to the bottom of this -// file before uncommenting it. -// - - -// -// Attacklab namespace -// -var Attacklab = Attacklab || {} - -// -// Showdown namespace -// -Attacklab.showdown = Attacklab.showdown || {} - -// -// converter -// -// Wraps all "globals" so that the only thing -// exposed is makeHtml(). -// -Attacklab.showdown.converter = function() { - -// -// Globals: -// - -// Global hashes, used by various utility routines -var g_urls; -var g_titles; -var g_html_blocks; - -// Used to track when we're inside an ordered or unordered list -// (see _ProcessListItems() for details): -var g_list_level = 0; - - -this.makeHtml = function(text) { -// -// Main function. The order in which other subs are called here is -// essential. Link and image substitutions need to happen before -// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the -// and tags get encoded. -// - - // Clear the global hashes. If we don't clear these, you get conflicts - // from other articles when generating a page which contains more than - // one article (e.g. an index page that shows the N most recent - // articles): - g_urls = new Array(); - g_titles = new Array(); - g_html_blocks = new Array(); - - // attacklab: Replace ~ with ~T - // This lets us use tilde as an escape char to avoid md5 hashes - // The choice of character is arbitray; anything that isn't - // magic in Markdown will work. - text = text.replace(/~/g,"~T"); - - // attacklab: Replace $ with ~D - // RegExp interprets $ as a special character - // when it's in a replacement string - text = text.replace(/\$/g,"~D"); - - // Standardize line endings - text = text.replace(/\r\n/g,"\n"); // DOS to Unix - text = text.replace(/\r/g,"\n"); // Mac to Unix - - // Make sure text begins and ends with a couple of newlines: - text = "\n\n" + text + "\n\n"; - - // Convert all tabs to spaces. - text = _Detab(text); - - // Strip any lines consisting only of spaces and tabs. - // This makes subsequent regexen easier to write, because we can - // match consecutive blank lines with /\n+/ instead of something - // contorted like /[ \t]*\n+/ . - text = text.replace(/^[ \t]+$/mg,""); - - // Turn block-level HTML blocks into hash entries - text = _HashHTMLBlocks(text); - - // Strip link definitions, store in hashes. - text = _StripLinkDefinitions(text); - - text = _RunBlockGamut(text); - - text = _UnescapeSpecialChars(text); - - // attacklab: Restore dollar signs - text = text.replace(/~D/g,"$$"); - - // attacklab: Restore tildes - text = text.replace(/~T/g,"~"); - - return text; -} - -var _StripLinkDefinitions = function(text) { -// -// Strips link definitions from text, stores the URLs and titles in -// hash references. -// - - // Link defs are in the form: ^[id]: url "optional title" - - /* - var text = text.replace(/ - ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 - [ \t]* - \n? // maybe *one* newline - [ \t]* - ? // url = $2 - [ \t]* - \n? // maybe one newline - [ \t]* - (?: - (\n*) // any lines skipped = $3 attacklab: lookbehind removed - ["(] - (.+?) // title = $4 - [")] - [ \t]* - )? // title is optional - (?:\n+|$) - /gm, - function(){...}); - */ - var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, - function (wholeMatch,m1,m2,m3,m4) { - m1 = m1.toLowerCase(); - g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive - if (m3) { - // Oops, found blank lines, so it's not a title. - // Put back the parenthetical statement we stole. - return m3+m4; - } else if (m4) { - g_titles[m1] = m4.replace(/"/g,"""); - } - - // Completely remove the definition from the text - return ""; - } - ); - - return text; -} - -var _HashHTMLBlocks = function(text) { - // attacklab: Double up blank lines to reduce lookaround - text = text.replace(/\n/g,"\n\n"); - - // Hashify HTML blocks: - // We only want to do this for block-level HTML tags, such as headers, - // lists, and tables. That's because we still want to wrap

      s around - // "paragraphs" that are wrapped in non-block-level tags, such as anchors, - // phrase emphasis, and spans. The list of tags we're looking for is - // hard-coded: - var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" - var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" - - // First, look for nested blocks, e.g.: - //

      - //
      - // tags for inner block must be indented. - //
      - //
      - // - // The outermost tags must start at the left margin for this to match, and - // the inner nested divs must be indented. - // We need to do this before the next, more liberal match, because the next - // match will start at the first `
      ` and stop at the first `
      `. - - // attacklab: This regex can be expensive when it fails. - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_a) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*?\n // any number of lines, minimally matching - // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); - - // - // Now match more liberally, simply from `\n` to `\n` - // - - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_b) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*? // any number of lines, minimally matching - .* // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); - - // Special case just for
      . It was easier to make a special case than - // to make the other regex more complicated. - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} - (<(hr) // start tag = $2 - \b // word break - ([^<>])*? // - \/?>) // the matching end tag - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); - - // Special case for standalone HTML comments: - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} // attacklab: g_tab_width - 1 - - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); - - // PHP and ASP-style processor instructions ( and <%...%>) - - /* - text = text.replace(/ - (?: - \n\n // Starting after a blank line - ) - ( // save in $1 - [ ]{0,3} // attacklab: g_tab_width - 1 - (?: - <([?%]) // $2 - [^\r]*? - \2> - ) - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); - - // attacklab: Undo double lines (see comment at top of this function) - text = text.replace(/\n\n/g,"\n"); - return text; -} - -var hashElement = function(wholeMatch,m1) { - var blockText = m1; - - // Undo double lines - blockText = blockText.replace(/\n\n/g,"\n"); - blockText = blockText.replace(/^\n/,""); - - // strip trailing blank lines - blockText = blockText.replace(/\n+$/g,""); - - // Replace the element text with a marker ("~KxK" where x is its key) - blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; - - return blockText; -}; - -var _RunBlockGamut = function(text) { -// -// These are all the transformations that form block-level -// tags like paragraphs, headers, and list items. -// - text = _DoHeaders(text); - - // Do Horizontal Rules: - var key = hashBlock("
      "); - text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,key); - - text = _DoLists(text); - text = _DoCodeBlocks(text); - text = _DoBlockQuotes(text); - - // We already ran _HashHTMLBlocks() before, in Markdown(), but that - // was to escape raw HTML in the original Markdown source. This time, - // we're escaping the markup we've just created, so that we don't wrap - //

      tags around block-level tags. - text = _HashHTMLBlocks(text); - text = _FormParagraphs(text); - - return text; -} - - -var _RunSpanGamut = function(text) { -// -// These are all the transformations that occur *within* block-level -// tags like paragraphs, headers, and list items. -// - - text = _DoCodeSpans(text); - text = _EscapeSpecialCharsWithinTagAttributes(text); - text = _EncodeBackslashEscapes(text); - - // Process anchor and image tags. Images must come first, - // because ![foo][f] looks like an anchor. - text = _DoImages(text); - text = _DoAnchors(text); - - // Make links out of things like `` - // Must come after _DoAnchors(), because you can use < and > - // delimiters in inline links like [this](). - text = _DoAutoLinks(text); - text = _EncodeAmpsAndAngles(text); - text = _DoItalicsAndBold(text); - - // Do hard breaks: - text = text.replace(/ +\n/g,"
      \n"); - - return text; -} - -var _EscapeSpecialCharsWithinTagAttributes = function(text) { -// -// Within tags -- meaning between < and > -- encode [\ ` * _] so they -// don't conflict with their use in Markdown for code, italics and strong. -// - - // Build a regex to find HTML tags and comments. See Friedl's - // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. - var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; - - text = text.replace(regex, function(wholeMatch) { - var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); - tag = escapeCharacters(tag,"\\`*_"); - return tag; - }); - - return text; -} - -var _DoAnchors = function(text) { -// -// Turn Markdown link shortcuts into XHTML
      tags. -// - // - // First, handle reference-style links: [link text] [id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[] // or anything else - )* - ) - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad remaining backreferences - /g,_DoAnchors_callback); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); - - // - // Next, inline-style links: [link text](url "optional title") - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[\]] // or anything else - ) - ) - \] - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // href = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // Title = $7 - \6 // matching quote - [ \t]* // ignore any spaces/tabs between closing quote and ) - )? // title is optional - \) - ) - /g,writeAnchorTag); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); - - // - // Last, handle reference-style shortcuts: [link text] - // These must come last in case you've also got [link test][1] - // or [link test](/foo) - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ([^\[\]]+) // link text = $2; can't contain '[' or ']' - \] - )()()()()() // pad rest of backreferences - /g, writeAnchorTag); - */ - text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); - - return text; -} - -var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - if (m7 == undefined) m7 = ""; - var whole_match = m1; - var link_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = link_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - if (whole_match.search(/\(\s*\)$/m)>-1) { - // Special case for explicit empty url - url = ""; - } else { - return whole_match; - } - } - } - - url = escapeCharacters(url,"*_"); - var result = ""; - - return result; -} - - -var _DoImages = function(text) { -// -// Turn Markdown image shortcuts into tags. -// - - // - // First, handle reference-style labeled images: ![alt text][id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad rest of backreferences - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); - - // - // Next, handle inline images: ![alt text](url "optional title") - // Don't forget: encode * and _ - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - \s? // One optional whitespace character - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // src url = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // title = $7 - \6 // matching quote - [ \t]* - )? // title is optional - \) - ) - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); - - return text; -} - -var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - var whole_match = m1; - var alt_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (!title) title = ""; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - return whole_match; - } - } - - alt_text = alt_text.replace(/"/g,"""); - url = escapeCharacters(url,"*_"); - var result = "\""" + _RunSpanGamut(m1) + "");}); - - text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function(matchFound,m1){return hashBlock("

      " + _RunSpanGamut(m1) + "

      ");}); - - // atx-style headers: - // # Header 1 - // ## Header 2 - // ## Header 2 with closing hashes ## - // ... - // ###### Header 6 - // - - /* - text = text.replace(/ - ^(\#{1,6}) // $1 = string of #'s - [ \t]* - (.+?) // $2 = Header text - [ \t]* - \#* // optional closing #'s (not counted) - \n+ - /gm, function() {...}); - */ - - text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, - function(wholeMatch,m1,m2) { - var h_level = m1.length; - return hashBlock("" + _RunSpanGamut(m2) + ""); - }); - - return text; -} - -// This declaration keeps Dojo compressor from outputting garbage: -var _ProcessListItems; - -var _DoLists = function(text) { -// -// Form HTML ordered (numbered) and unordered (bulleted) lists. -// - - // attacklab: add sentinel to hack around khtml/safari bug: - // http://bugs.webkit.org/show_bug.cgi?id=11231 - text += "~0"; - - // Re-usable pattern to match any entirel ul or ol list: - - /* - var whole_list = / - ( // $1 = whole list - ( // $2 - [ ]{0,3} // attacklab: g_tab_width - 1 - ([*+-]|\d+[.]) // $3 = first list item marker - [ \t]+ - ) - [^\r]+? - ( // $4 - ~0 // sentinel for workaround; should be $ - | - \n{2,} - (?=\S) - (?! // Negative lookahead for another list item marker - [ \t]* - (?:[*+-]|\d+[.])[ \t]+ - ) - ) - )/g - */ - var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; - - if (g_list_level) { - text = text.replace(whole_list,function(wholeMatch,m1,m2) { - var list = m1; - var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; - - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - - // Trim any trailing whitespace, to put the closing `` - // up on the preceding line, to get it past the current stupid - // HTML block parser. This is a hack to work around the terrible - // hack that is the HTML block parser. - result = result.replace(/\s+$/,""); - result = "<"+list_type+">" + result + "\n"; - return result; - }); - } else { - whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; - text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { - var runup = m1; - var list = m2; - - var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - var list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - result = runup + "<"+list_type+">\n" + result + "\n"; - return result; - }); - } - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -_ProcessListItems = function(list_str) { -// -// Process the contents of a single ordered or unordered list, splitting it -// into individual list items. -// - // The $g_list_level global keeps track of when we're inside a list. - // Each time we enter a list, we increment it; when we leave a list, - // we decrement. If it's zero, we're not in a list anymore. - // - // We do this because when we're not inside a list, we want to treat - // something like this: - // - // I recommend upgrading to version - // 8. Oops, now this line is treated - // as a sub-list. - // - // As a single paragraph, despite the fact that the second line starts - // with a digit-period-space sequence. - // - // Whereas when we're inside a list (or sub-list), that line will be - // treated as the start of a sub-list. What a kludge, huh? This is - // an aspect of Markdown's syntax that's hard to parse perfectly - // without resorting to mind-reading. Perhaps the solution is to - // change the syntax rules such that sub-lists must start with a - // starting cardinal number; e.g. "1." or "a.". - - g_list_level++; - - // trim trailing blank lines: - list_str = list_str.replace(/\n{2,}$/,"\n"); - - // attacklab: add sentinel to emulate \z - list_str += "~0"; - - /* - list_str = list_str.replace(/ - (\n)? // leading line = $1 - (^[ \t]*) // leading whitespace = $2 - ([*+-]|\d+[.]) [ \t]+ // list marker = $3 - ([^\r]+? // list item text = $4 - (\n{1,2})) - (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) - /gm, function(){...}); - */ - list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, - function(wholeMatch,m1,m2,m3,m4){ - var item = m4; - var leading_line = m1; - var leading_space = m2; - - if (leading_line || (item.search(/\n{2,}/)>-1)) { - item = _RunBlockGamut(_Outdent(item)); - } - else { - // Recursion for sub-lists: - item = _DoLists(_Outdent(item)); - item = item.replace(/\n$/,""); // chomp(item) - item = _RunSpanGamut(item); - } - - return "
    1. " + item + "
    2. \n"; - } - ); - - // attacklab: strip sentinel - list_str = list_str.replace(/~0/g,""); - - g_list_level--; - return list_str; -} - - -var _DoCodeBlocks = function(text) { -// -// Process Markdown `
      ` blocks.
      -//  
      -
      -	/*
      -		text = text.replace(text,
      -			/(?:\n\n|^)
      -			(								// $1 = the code block -- one or more lines, starting with a space/tab
      -				(?:
      -					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
      -					.*\n+
      -				)+
      -			)
      -			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
      -		/g,function(){...});
      -	*/
      -
      -	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
      -	text += "~0";
      -	
      -	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
      -		function(wholeMatch,m1,m2) {
      -			var codeblock = m1;
      -			var nextChar = m2;
      -		
      -			codeblock = _EncodeCode( _Outdent(codeblock));
      -			codeblock = _Detab(codeblock);
      -			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
      -			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
      -
      -			codeblock = "
      " + codeblock + "\n
      "; - - return hashBlock(codeblock) + nextChar; - } - ); - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -var hashBlock = function(text) { - text = text.replace(/(^\n+|\n+$)/g,""); - return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; -} - - -var _DoCodeSpans = function(text) { -// -// * Backtick quotes are used for spans. -// -// * You can use multiple backticks as the delimiters if you want to -// include literal backticks in the code span. So, this input: -// -// Just type ``foo `bar` baz`` at the prompt. -// -// Will translate to: -// -//

      Just type foo `bar` baz at the prompt.

      -// -// There's no arbitrary limit to the number of backticks you -// can use as delimters. If you need three consecutive backticks -// in your code, use four for delimiters, etc. -// -// * You can use spaces to get literal backticks at the edges: -// -// ... type `` `bar` `` ... -// -// Turns to: -// -// ... type `bar` ... -// - - /* - text = text.replace(/ - (^|[^\\]) // Character before opening ` can't be a backslash - (`+) // $2 = Opening run of ` - ( // $3 = The code block - [^\r]*? - [^`] // attacklab: work around lack of lookbehind - ) - \2 // Matching closer - (?!`) - /gm, function(){...}); - */ - - text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, - function(wholeMatch,m1,m2,m3,m4) { - var c = m3; - c = c.replace(/^([ \t]*)/g,""); // leading whitespace - c = c.replace(/[ \t]*$/g,""); // trailing whitespace - c = _EncodeCode(c); - return m1+""+c+""; - }); - - return text; -} - - -var _EncodeCode = function(text) { -// -// Encode/escape certain characters inside Markdown code runs. -// The point is that in code, these characters are literals, -// and lose their special Markdown meanings. -// - // Encode all ampersands; HTML entities are not - // entities within a Markdown code span. - text = text.replace(/&/g,"&"); - - // Do the angle bracket song and dance: - text = text.replace(//g,">"); - - // Now, escape characters that are magic in Markdown: - text = escapeCharacters(text,"\*_{}[]\\",false); - -// jj the line above breaks this: -//--- - -//* Item - -// 1. Subitem - -// special char: * -//--- - - return text; -} - - -var _DoItalicsAndBold = function(text) { - - // must go first: - text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, - "$1$3$4"); - - text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, - "$1$3$4"); - - return text; -} - - -var _DoBlockQuotes = function(text) { - - /* - text = text.replace(/ - ( // Wrap whole match in $1 - ( - ^[ \t]*>[ \t]? // '>' at the start of a line - .+\n // rest of the first line - (.+\n)* // subsequent consecutive lines - \n* // blanks - )+ - ) - /gm, function(){...}); - */ - - text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, - function(wholeMatch,m1) { - var bq = m1; - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting - - // attacklab: clean up hack - bq = bq.replace(/~0/g,""); - - bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines - bq = _RunBlockGamut(bq); // recurse - - bq = bq.replace(/(^|\n)/g,"$1 "); - // These leading spaces screw with
       content, so we need to fix that:
      -			bq = bq.replace(
      -					/(\s*
      [^\r]+?<\/pre>)/gm,
      -				function(wholeMatch,m1) {
      -					var pre = m1;
      -					// attacklab: hack around Konqueror 3.5.4 bug:
      -					pre = pre.replace(/^  /mg,"~0");
      -					pre = pre.replace(/~0/g,"");
      -					return pre;
      -				});
      -			
      -			return hashBlock("
      \n" + bq + "\n
      "); - }); - return text; -} - - -var _FormParagraphs = function(text) { -// -// Params: -// $text - string to process with html

      tags -// - - // Strip leading and trailing lines: - text = text.replace(/^\n+/g,""); - text = text.replace(/\n+$/g,""); - - var grafs = text.split(/\n{2,}/g); - var grafsOut = new Array(); - - // - // Wrap

      tags. - // - var end = grafs.length; - for (var i=0; i= 0) { - grafsOut.push(str); - } - else if (str.search(/\S/) >= 0) { - str = _RunSpanGamut(str); - str = str.replace(/^([ \t]*)/g,"

      "); - str += "

      " - grafsOut.push(str); - } - - } - - // - // Unhashify HTML blocks - // - end = grafsOut.length; - for (var i=0; i= 0) { - var blockText = g_html_blocks[RegExp.$1]; - blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs - grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); - } - } - - return grafsOut.join("\n\n"); -} - - -var _EncodeAmpsAndAngles = function(text) { -// Smart processing for ampersands and angle brackets that need to be encoded. - - // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - // http://bumppo.net/projects/amputator/ - text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); - - // Encode naked <'s - text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); - - return text; -} - - -var _EncodeBackslashEscapes = function(text) { -// -// Parameter: String. -// Returns: The string, with after processing the following backslash -// escape sequences. -// - - // attacklab: The polite way to do this is with the new - // escapeCharacters() function: - // - // text = escapeCharacters(text,"\\",true); - // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); - // - // ...but we're sidestepping its use of the (slow) RegExp constructor - // as an optimization for Firefox. This function gets called a LOT. - - text = text.replace(/\\(\\)/g,escapeCharacters_callback); - text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); - return text; -} - - -var _DoAutoLinks = function(text) { - - // note that at this point, all other URL in the text are already hyperlinked as
      - // *except* for the case - - // automatically add < and > around unadorned raw hyperlinks - // must be preceded by space/BOF and followed by non-word/EOF character - text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4"); - - // autolink anything like - text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, "$1"); - - // Email addresses: - /* - text = text.replace(/ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - /gi, _DoAutoLinks_callback()); - */ - /* disabling email autolinking, since we don't do that on the server, either - text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, - function(wholeMatch,m1) { - return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); - } - ); - */ - - return text; -} - - -var _UnescapeSpecialChars = function(text) { -// -// Swap back in all the special characters we've hidden. -// - text = text.replace(/~E(\d+)E/g, - function(wholeMatch,m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - } - ); - return text; -} - - -var _Outdent = function(text) { -// -// Remove one level of line-leading tabs or spaces -// - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width - - // attacklab: clean up hack - text = text.replace(/~0/g,"") - - return text; -} - -var _Detab = function(text) { -// attacklab: Detab's completely rewritten for speed. -// In perl we could fix it by anchoring the regexp with \G. -// In javascript we're less fortunate. - - // expand first n-1 tabs - text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width - - // replace the nth with two sentinels - text = text.replace(/\t/g,"~A~B"); - - // use the sentinel to anchor our regex so it doesn't explode - text = text.replace(/~B(.+?)~A/g, - function(wholeMatch,m1,m2) { - var leadingText = m1; - var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width - - // there *must* be a better way to do this: - for (var i=0; i"; - var imageDialogText = "

      Insert Image

      http://example.com/images/diagram.jpg \"optional title\"

      Need free image hosting?

      "; - - // The default text that appears in the dialog input box when entering - // links. - var imageDefaultText = "http://"; - var linkDefaultText = "http://"; - - // The location of your button images relative to the base directory. - var imageDirectory = "/Content/Img/"; - - // The link and title for the help button - var helpLink = "/editing-help"; - var helpHoverTitle = "Markdown Editing Help"; - - // ------------------------------------------------------------------- - // END OF YOUR CHANGES - // ------------------------------------------------------------------- - - // A collection of the important regions on the page. - // Cached so we don't have to keep traversing the DOM. - wmd.PanelCollection = function(){ - this.buttonBar = doc.getElementById("wmd-button-bar"); - this.preview = doc.getElementById("wmd-preview"); - this.buffer = doc.getElementById("wmd-buffer"); // added by Anton - this.output = doc.getElementById("wmd-output"); - this.input = doc.getElementById("wmd-input"); - - this.SwapBuffers = function () { // added by Anton - var buffer = this.preview, preview = this.buffer; - this.buffer = buffer; this.preview = preview; - buffer.style.visibility = "hidden"; buffer.style.position = "absolute"; - preview.style.position = ""; preview.style.visibility = ""; - truepreview = (!truepreview); - }; - }; - - // This PanelCollection object can't be filled until after the page - // has loaded. - wmd.panels = undefined; - - // Internet explorer has problems with CSS sprite buttons that use HTML - // lists. When you click on the background image "button", IE will - // select the non-existent link text and discard the selection in the - // textarea. The solution to this is to cache the textarea selection - // on the button's mousedown event and set a flag. In the part of the - // code where we need to grab the selection, we check for the flag - // and, if it's set, use the cached area instead of querying the - // textarea. - // - // This ONLY affects Internet Explorer (tested on versions 6, 7 - // and 8) and ONLY on button clicks. Keyboard shortcuts work - // normally since the focus never leaves the textarea. - wmd.ieCachedRange = null; // cached textarea selection - wmd.ieRetardedClick = false; // flag - - // Returns true if the DOM element is visible, false if it's hidden. - // Checks if display is anything other than none. - util.isVisible = function (elem) { - - if (window.getComputedStyle) { - // Most browsers - return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none"; - } - else if (elem.currentStyle) { - // IE - return elem.currentStyle["display"] !== "none"; - } - }; - - - // Adds a listener callback to a DOM element which is fired on a specified - // event. - util.addEvent = function(elem, event, listener){ - if (elem.attachEvent) { - // IE only. The "on" is mandatory. - elem.attachEvent("on" + event, listener); - } - else { - // Other browsers. - elem.addEventListener(event, listener, false); - } - }; - - - // Removes a listener callback from a DOM element which is fired on a specified - // event. - util.removeEvent = function(elem, event, listener){ - if (elem.detachEvent) { - // IE only. The "on" is mandatory. - elem.detachEvent("on" + event, listener); - } - else { - // Other browsers. - elem.removeEventListener(event, listener, false); - } - }; - - // Converts \r\n and \r to \n. - util.fixEolChars = function(text){ - text = text.replace(/\r\n/g, "\n"); - text = text.replace(/\r/g, "\n"); - return text; - }; - - // Extends a regular expression. Returns a new RegExp - // using pre + regex + post as the expression. - // Used in a few functions where we have a base - // expression and we want to pre- or append some - // conditions to it (e.g. adding "$" to the end). - // The flags are unchanged. - // - // regex is a RegExp, pre and post are strings. - util.extendRegExp = function(regex, pre, post){ - - if (pre === null || pre === undefined) - { - pre = ""; - } - if(post === null || post === undefined) - { - post = ""; - } - - var pattern = regex.toString(); - var flags; - - // Replace the flags with empty space and store them. - pattern = pattern.replace(/\/([gim]*)$/, ""); - flags = re.$1; - - // Remove the slash delimiters on the regular expression. - pattern = pattern.replace(/(^\/|\/$)/g, ""); - pattern = pre + pattern + post; - - return new re(pattern, flags); - } - - - // Sets the image for a button passed to the WMD editor. - // Returns a new element with the image attached. - // Adds several style properties to the image. - util.createImage = function(img){ - - var imgPath = imageDirectory + img; - - var elem = doc.createElement("img"); - elem.className = "wmd-button"; - elem.src = imgPath; - - return elem; - }; - - - // This simulates a modal dialog box and asks for the URL when you - // click the hyperlink or image buttons. - // - // text: The html for the input box. - // defaultInputText: The default value that appears in the input box. - // makeLinkMarkdown: The function which is executed when the prompt is dismissed, either via OK or Cancel - util.prompt = function(text, defaultInputText, makeLinkMarkdown){ - - // These variables need to be declared at this level since they are used - // in multiple functions. - var dialog; // The dialog box. - var background; // The background beind the dialog box. - var input; // The text box where you enter the hyperlink. - - - if (defaultInputText === undefined) { - defaultInputText = ""; - } - - // Used as a keydown event handler. Esc dismisses the prompt. - // Key code 27 is ESC. - var checkEscape = function(key){ - var code = (key.charCode || key.keyCode); - if (code === 27) { - close(true); - } - }; - - // Dismisses the hyperlink input box. - // isCancel is true if we don't care about the input text. - // isCancel is false if we are going to keep the text. - var close = function(isCancel){ - util.removeEvent(doc.body, "keydown", checkEscape); - var text = input.value; - - if (isCancel){ - text = null; - } - else{ - // Fixes common pasting errors. - text = text.replace('http://http://', 'http://'); - text = text.replace('http://https://', 'https://'); - text = text.replace('http://ftp://', 'ftp://'); - - if (text.indexOf('http://') === -1 && text.indexOf('ftp://') === -1 && text.indexOf('https://') === -1) { - text = 'http://' + text; - } - } - - dialog.parentNode.removeChild(dialog); - background.parentNode.removeChild(background); - makeLinkMarkdown(text); - return false; - }; - - // Creates the background behind the hyperlink text entry box. - // Most of this has been moved to CSS but the div creation and - // browser-specific hacks remain here. - var createBackground = function(){ - - background = doc.createElement("div"); - background.className = "wmd-prompt-background"; - style = background.style; - style.position = "absolute"; - style.top = "0"; - - style.zIndex = "1000"; - - if (global.isIE){ - style.filter = "alpha(opacity=50)"; - } - else { - style.opacity = "0.5"; - } - - var pageSize = position.getPageSize(); - style.height = pageSize[1] + "px"; - - if(global.isIE){ - style.left = doc.documentElement.scrollLeft; - style.width = doc.documentElement.clientWidth; - } - else { - style.left = "0"; - style.width = "100%"; - } - - doc.body.appendChild(background); - }; - - // Create the text input box form/window. - var createDialog = function(){ - - // The main dialog box. - dialog = doc.createElement("div"); - dialog.className = "wmd-prompt-dialog"; - dialog.style.padding = "10px;"; - dialog.style.position = "fixed"; - dialog.style.width = "400px"; - dialog.style.zIndex = "1001"; - - // The dialog text. - var question = doc.createElement("div"); - question.innerHTML = text; - question.style.padding = "5px"; - dialog.appendChild(question); - - // The web form container for the text box and buttons. - var form = doc.createElement("form"); - form.onsubmit = function(){ return close(false); }; - style = form.style; - style.padding = "0"; - style.margin = "0"; - style.cssFloat = "left"; - style.width = "100%"; - style.textAlign = "center"; - style.position = "relative"; - dialog.appendChild(form); - - // The input text box - input = doc.createElement("input"); - input.type = "text"; - input.value = defaultInputText; - style = input.style; - style.display = "block"; - style.width = "80%"; - style.marginLeft = style.marginRight = "auto"; - form.appendChild(input); - - // The ok button - var okButton = doc.createElement("input"); - okButton.type = "button"; - okButton.onclick = function(){ return close(false); }; - okButton.value = "OK"; - style = okButton.style; - style.margin = "10px"; - style.display = "inline"; - style.width = "7em"; - - - // The cancel button - var cancelButton = doc.createElement("input"); - cancelButton.type = "button"; - cancelButton.onclick = function(){ return close(true); }; - cancelButton.value = "Cancel"; - style = cancelButton.style; - style.margin = "10px"; - style.display = "inline"; - style.width = "7em"; - - // The order of these buttons is different on macs. - if (/mac/.test(nav.platform.toLowerCase())) { - form.appendChild(cancelButton); - form.appendChild(okButton); - } - else { - form.appendChild(okButton); - form.appendChild(cancelButton); - } - - util.addEvent(doc.body, "keydown", checkEscape); - dialog.style.top = "50%"; - dialog.style.left = "50%"; - dialog.style.display = "block"; - if(global.isIE_5or6){ - dialog.style.position = "absolute"; - dialog.style.top = doc.documentElement.scrollTop + 200 + "px"; - dialog.style.left = "50%"; - } - doc.body.appendChild(dialog); - - // This has to be done AFTER adding the dialog to the form if you - // want it to be centered. - dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px"; - dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px"; - - }; - - createBackground(); - - // Why is this in a zero-length timeout? - // Is it working around a browser bug? - window.setTimeout(function(){ - - createDialog(); - - var defTextLen = defaultInputText.length; - if (input.selectionStart !== undefined) { - input.selectionStart = 0; - input.selectionEnd = defTextLen; - } - else if (input.createTextRange) { - var range = input.createTextRange(); - range.collapse(false); - range.moveStart("character", -defTextLen); - range.moveEnd("character", defTextLen); - range.select(); - } - - input.focus(); - }, 0); - }; - - - // UNFINISHED - // The assignment in the while loop makes jslint cranky. - // I'll change it to a better loop later. - position.getTop = function(elem, isInner){ - var result = elem.offsetTop; - if (!isInner) { - while (elem = elem.offsetParent) { - result += elem.offsetTop; - } - } - return result; - }; - - position.getHeight = function (elem) { - return elem.offsetHeight || elem.scrollHeight; - }; - - position.getWidth = function (elem) { - return elem.offsetWidth || elem.scrollWidth; - }; - - position.getPageSize = function(){ - - var scrollWidth, scrollHeight; - var innerWidth, innerHeight; - - // It's not very clear which blocks work with which browsers. - if(self.innerHeight && self.scrollMaxY){ - scrollWidth = doc.body.scrollWidth; - scrollHeight = self.innerHeight + self.scrollMaxY; - } - else if(doc.body.scrollHeight > doc.body.offsetHeight){ - scrollWidth = doc.body.scrollWidth; - scrollHeight = doc.body.scrollHeight; - } - else{ - scrollWidth = doc.body.offsetWidth; - scrollHeight = doc.body.offsetHeight; - } - - if(self.innerHeight){ - // Non-IE browser - innerWidth = self.innerWidth; - innerHeight = self.innerHeight; - } - else if(doc.documentElement && doc.documentElement.clientHeight){ - // Some versions of IE (IE 6 w/ a DOCTYPE declaration) - innerWidth = doc.documentElement.clientWidth; - innerHeight = doc.documentElement.clientHeight; - } - else if(doc.body){ - // Other versions of IE - innerWidth = doc.body.clientWidth; - innerHeight = doc.body.clientHeight; - } - - var maxWidth = Math.max(scrollWidth, innerWidth); - var maxHeight = Math.max(scrollHeight, innerHeight); - return [maxWidth, maxHeight, innerWidth, innerHeight]; - }; - - // Handles pushing and popping TextareaStates for undo/redo commands. - // I should rename the stack variables to list. - wmd.undoManager = function(callback){ - - var undoObj = this; - var undoStack = []; // A stack of undo states - var stackPtr = 0; // The index of the current state - var mode = "none"; - var lastState; // The last state - var poller; - var timer; // The setTimeout handle for cancelling the timer - var inputStateObj; - - // Set the mode for later logic steps. - var setMode = function(newMode, noSave){ - - if (mode != newMode) { - mode = newMode; - if (!noSave) { - saveState(); - } - } - - if (!global.isIE || mode != "moving") { - timer = window.setTimeout(refreshState, 1); - } - else { - inputStateObj = null; - } - }; - - var refreshState = function(){ - inputStateObj = new wmd.TextareaState(); - timer = undefined; - }; - - this.setCommandMode = function(){ - mode = "command"; - saveState(); - timer = window.setTimeout(refreshState, 0); - }; - - this.canUndo = function(){ - return stackPtr > 1; - }; - - this.canRedo = function(){ - if (undoStack[stackPtr + 1]) { - return true; - } - return false; - }; - - // Removes the last state and restores it. - this.undo = function(){ - - if (undoObj.canUndo()) { - if (lastState) { - // What about setting state -1 to null or checking for undefined? - lastState.restore(); - lastState = null; - } - else { - undoStack[stackPtr] = new wmd.TextareaState(); - undoStack[--stackPtr].restore(); - - if (callback) { - callback(); - } - } - } - - mode = "none"; - wmd.panels.input.focus(); - refreshState(); - }; - - // Redo an action. - this.redo = function(){ - - if (undoObj.canRedo()) { - - undoStack[++stackPtr].restore(); - - if (callback) { - callback(); - } - } - - mode = "none"; - wmd.panels.input.focus(); - refreshState(); - }; - - // Push the input area state to the stack. - var saveState = function(){ - - var currState = inputStateObj || new wmd.TextareaState(); - - if (!currState) { - return false; - } - if (mode == "moving") { - if (!lastState) { - lastState = currState; - } - return; - } - if (lastState) { - if (undoStack[stackPtr - 1].text != lastState.text) { - undoStack[stackPtr++] = lastState; - } - lastState = null; - } - undoStack[stackPtr++] = currState; - undoStack[stackPtr + 1] = null; - if (callback) { - callback(); - } - }; - - var handleCtrlYZ = function(event){ - - var handled = false; - - // check for !event.altKey to support Polish - if ((event.ctrlKey || event.metaKey) && !event.altKey) { - - // IE and Opera do not support charCode. - var keyCode = event.charCode || event.keyCode; - var keyCodeChar = String.fromCharCode(keyCode); - - switch (keyCodeChar) { - - case "y": - undoObj.redo(); - handled = true; - break; - - case "z": - if (!event.shiftKey) { - undoObj.undo(); - } - else { - undoObj.redo(); - } - handled = true; - break; - } - } - - if (handled) { - if (event.preventDefault) { - event.preventDefault(); - } - if (window.event) { - window.event.returnValue = false; - } - return; - } - }; - - // Set the mode depending on what is going on in the input area. - var handleModeChange = function(event){ - - if (!event.ctrlKey && !event.metaKey) { - - var keyCode = event.keyCode; - - if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) { - // 33 - 40: page up/dn and arrow keys - // 63232 - 63235: page up/dn and arrow keys on safari - setMode("moving"); - } - else if (keyCode == 8 || keyCode == 46 || keyCode == 127) { - // 8: backspace - // 46: delete - // 127: delete - setMode("deleting"); - } - else if (keyCode == 13) { - // 13: Enter - setMode("newlines"); - } - else if (keyCode == 27) { - // 27: escape - setMode("escape"); - } - else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) { - // 16-20 are shift, etc. - // 91: left window key - // I think this might be a little messed up since there are - // a lot of nonprinting keys above 20. - setMode("typing"); - } - } - }; - - var setEventHandlers = function(){ - - util.addEvent(wmd.panels.input, "keypress", function(event){ - // keyCode 89: y - // keyCode 90: z - // check for !event.altkey to support Polish - if ((event.ctrlKey || event.metaKey) && !event.altKey && (event.keyCode == 89 || event.keyCode == 90)) { - event.preventDefault(); - } - }); - - var handlePaste = function(){ - if (global.isIE || (inputStateObj && inputStateObj.text != wmd.panels.input.value)) { - if (timer == undefined) { - mode = "paste"; - saveState(); - refreshState(); - } - } - }; - - util.addEvent(wmd.panels.input, "keydown", handleCtrlYZ); - util.addEvent(wmd.panels.input, "keydown", handleModeChange); - util.addEvent(wmd.panels.input, "mousedown", function(){ - setMode("moving"); - }); - - wmd.panels.input.onpaste = handlePaste; - wmd.panels.input.ondrop = handlePaste; - }; - - var init = function(){ - setEventHandlers(); - refreshState(); - saveState(); - }; - -// this.destroy = function(){ -// if (poller) { -// poller.destroy(); -// } -// }; - - init(); - }; - - // I think my understanding of how the buttons and callbacks are stored in the array is incomplete. - wmd.editor = function(previewRefreshCallback){ - - if (!previewRefreshCallback) { - previewRefreshCallback = function(){}; - } - - var inputBox = wmd.panels.input; - - var offsetHeight = 0; - - var editObj = this; - - var mainDiv; - var mainSpan; - - var div; // This name is pretty ambiguous. I should rename this. - - // Used to cancel recurring events from setInterval. - var creationHandle; - - var undoMgr; // The undo manager - - // Perform the button's action. - var doClick = function(button){ - - inputBox.focus(); - - if (button.textOp) { - - if (undoMgr) { - undoMgr.setCommandMode(); - } - - var state = new wmd.TextareaState(); - - if (!state) { - return; - } - - var chunks = state.getChunks(); - - // Some commands launch a "modal" prompt dialog. Javascript - // can't really make a modal dialog box and the WMD code - // will continue to execute while the dialog is displayed. - // This prevents the dialog pattern I'm used to and means - // I can't do something like this: - // - // var link = CreateLinkDialog(); - // makeMarkdownLink(link); - // - // Instead of this straightforward method of handling a - // dialog I have to pass any code which would execute - // after the dialog is dismissed (e.g. link creation) - // in a function parameter. - // - // Yes this is awkward and I think it sucks, but there's - // no real workaround. Only the image and link code - // create dialogs and require the function pointers. - var fixupInputArea = function(){ - - inputBox.focus(); - - if (chunks) { - state.setChunks(chunks); - } - - state.restore(); - previewRefreshCallback(); - }; - - var noCleanup = button.textOp(chunks, fixupInputArea); - - if(!noCleanup) { - fixupInputArea(); - } - - } - - if (button.execute) { - button.execute(editObj); - } - }; - - var setUndoRedoButtonStates = function(){ - if(undoMgr){ - setupButton(document.getElementById("wmd-undo-button"), undoMgr.canUndo()); - setupButton(document.getElementById("wmd-redo-button"), undoMgr.canRedo()); - } - }; - - var setupButton = function(button, isEnabled) { - - var normalYShift = "0px"; - var disabledYShift = "-20px"; - var highlightYShift = "-40px"; - - if(isEnabled) { - button.style.backgroundPosition = button.XShift + " " + normalYShift; - button.onmouseover = function(){ - this.style.backgroundPosition = this.XShift + " " + highlightYShift; - }; - - button.onmouseout = function(){ - this.style.backgroundPosition = this.XShift + " " + normalYShift; - }; - - // IE tries to select the background image "button" text (it's - // implemented in a list item) so we have to cache the selection - // on mousedown. - if(global.isIE) { - button.onmousedown = function() { - wmd.ieRetardedClick = true; - wmd.ieCachedRange = document.selection.createRange(); - }; - } - - if (!button.isHelp) - { - button.onclick = function() { - if (this.onmouseout) { - this.onmouseout(); - } - doClick(this); - return false; - } - } - } - else { - button.style.backgroundPosition = button.XShift + " " + disabledYShift; - button.onmouseover = button.onmouseout = button.onclick = function(){}; - } - } - - var makeSpritedButtonRow = function(){ - - var buttonBar = document.getElementById("wmd-button-bar"); - - var normalYShift = "0px"; - var disabledYShift = "-20px"; - var highlightYShift = "-40px"; - - var buttonRow = document.createElement("ul"); - buttonRow.id = "wmd-button-row"; - buttonRow = buttonBar.appendChild(buttonRow); - - var boldButton = document.createElement("li"); - boldButton.className = "wmd-button"; - boldButton.id = "wmd-bold-button"; - boldButton.title = "Strong Ctrl+B"; - boldButton.XShift = "0px"; - boldButton.textOp = command.doBold; - setupButton(boldButton, true); - buttonRow.appendChild(boldButton); - - var italicButton = document.createElement("li"); - italicButton.className = "wmd-button"; - italicButton.id = "wmd-italic-button"; - italicButton.title = "Emphasis Ctrl+I"; - italicButton.XShift = "-20px"; - italicButton.textOp = command.doItalic; - setupButton(italicButton, true); - buttonRow.appendChild(italicButton); - - var spacer1 = document.createElement("li"); - spacer1.className = "wmd-spacer"; - spacer1.id = "wmd-spacer1"; - buttonRow.appendChild(spacer1); - - var linkButton = document.createElement("li"); - linkButton.className = "wmd-button"; - linkButton.id = "wmd-link-button"; - linkButton.title = "Hyperlink Ctrl+L"; - linkButton.XShift = "-40px"; - linkButton.textOp = function(chunk, postProcessing){ - return command.doLinkOrImage(chunk, postProcessing, false); - }; - setupButton(linkButton, true); - buttonRow.appendChild(linkButton); - - var quoteButton = document.createElement("li"); - quoteButton.className = "wmd-button"; - quoteButton.id = "wmd-quote-button"; - quoteButton.title = "Blockquote
      Ctrl+Q"; - quoteButton.XShift = "-60px"; - quoteButton.textOp = command.doBlockquote; - setupButton(quoteButton, true); - buttonRow.appendChild(quoteButton); - - var codeButton = document.createElement("li"); - codeButton.className = "wmd-button"; - codeButton.id = "wmd-code-button"; - codeButton.title = "Code Sample
       Ctrl+K";
      -			codeButton.XShift = "-80px";
      -			codeButton.textOp = command.doCode;
      -			setupButton(codeButton, true);
      -			buttonRow.appendChild(codeButton);
      -
      -			var imageButton = document.createElement("li");
      -			imageButton.className = "wmd-button";
      -			imageButton.id = "wmd-image-button";
      -			imageButton.title = "Image  Ctrl+G";
      -			imageButton.XShift = "-100px";
      -			imageButton.textOp = function(chunk, postProcessing){
      -				return command.doLinkOrImage(chunk, postProcessing, true);
      -			};
      -			setupButton(imageButton, true);
      -			buttonRow.appendChild(imageButton);
      -
      -			var spacer2 = document.createElement("li");
      -			spacer2.className = "wmd-spacer";
      -			spacer2.id = "wmd-spacer2";
      -			buttonRow.appendChild(spacer2); 
      -
      -			var olistButton = document.createElement("li");
      -			olistButton.className = "wmd-button";
      -			olistButton.id = "wmd-olist-button";
      -			olistButton.title = "Numbered List 
        Ctrl+O"; - olistButton.XShift = "-120px"; - olistButton.textOp = function(chunk, postProcessing){ - command.doList(chunk, postProcessing, true); - }; - setupButton(olistButton, true); - buttonRow.appendChild(olistButton); - - var ulistButton = document.createElement("li"); - ulistButton.className = "wmd-button"; - ulistButton.id = "wmd-ulist-button"; - ulistButton.title = "Bulleted List
          Ctrl+U"; - ulistButton.XShift = "-140px"; - ulistButton.textOp = function(chunk, postProcessing){ - command.doList(chunk, postProcessing, false); - }; - setupButton(ulistButton, true); - buttonRow.appendChild(ulistButton); - - var headingButton = document.createElement("li"); - headingButton.className = "wmd-button"; - headingButton.id = "wmd-heading-button"; - headingButton.title = "Heading

          /

          Ctrl+H"; - headingButton.XShift = "-160px"; - headingButton.textOp = command.doHeading; - setupButton(headingButton, true); - buttonRow.appendChild(headingButton); - - var hrButton = document.createElement("li"); - hrButton.className = "wmd-button"; - hrButton.id = "wmd-hr-button"; - hrButton.title = "Horizontal Rule
          Ctrl+R"; - hrButton.XShift = "-180px"; - hrButton.textOp = command.doHorizontalRule; - setupButton(hrButton, true); - buttonRow.appendChild(hrButton); - - var spacer3 = document.createElement("li"); - spacer3.className = "wmd-spacer"; - spacer3.id = "wmd-spacer3"; - buttonRow.appendChild(spacer3); - - var undoButton = document.createElement("li"); - undoButton.className = "wmd-button"; - undoButton.id = "wmd-undo-button"; - undoButton.title = "Undo - Ctrl+Z"; - undoButton.XShift = "-200px"; - undoButton.execute = function(manager){ - manager.undo(); - }; - setupButton(undoButton, true); - buttonRow.appendChild(undoButton); - - var redoButton = document.createElement("li"); - redoButton.className = "wmd-button"; - redoButton.id = "wmd-redo-button"; - redoButton.title = "Redo - Ctrl+Y"; - if (/win/.test(nav.platform.toLowerCase())) { - redoButton.title = "Redo - Ctrl+Y"; - } - else { - // mac and other non-Windows platforms - redoButton.title = "Redo - Ctrl+Shift+Z"; - } - redoButton.XShift = "-220px"; - redoButton.execute = function(manager){ - manager.redo(); - }; - setupButton(redoButton, true); - buttonRow.appendChild(redoButton); - - var helpButton = document.createElement("li"); - helpButton.className = "wmd-button"; - helpButton.id = "wmd-help-button"; - helpButton.XShift = "-240px"; - helpButton.isHelp = true; - var helpAnchor = document.createElement("a"); - helpAnchor.href = helpLink; - helpAnchor.target = "_blank"; - helpAnchor.title = helpHoverTitle; - helpButton.appendChild(helpAnchor); - setupButton(helpButton, true); - buttonRow.appendChild(helpButton); - - setUndoRedoButtonStates(); - } - - var setupEditor = function(){ - - if (/\?noundo/.test(doc.location.href)) { - wmd.nativeUndo = true; - } - - if (!wmd.nativeUndo) { - undoMgr = new wmd.undoManager(function(){ - previewRefreshCallback(); - setUndoRedoButtonStates(); - }); - } - - makeSpritedButtonRow(); - - - var keyEvent = "keydown"; - if (global.isOpera) { - keyEvent = "keypress"; - } - - util.addEvent(inputBox, keyEvent, function(key){ - - // Check to see if we have a button key and, if so execute the callback. Check for !key.altKey to support Polish. - if ((key.ctrlKey || key.metaKey) && !key.altKey) { - - var keyCode = key.charCode || key.keyCode; - var keyCodeStr = String.fromCharCode(keyCode).toLowerCase(); - - switch(keyCodeStr) { - case "b": - doClick(document.getElementById("wmd-bold-button")); - break; - case "i": - doClick(document.getElementById("wmd-italic-button")); - break; - case "l": - doClick(document.getElementById("wmd-link-button")); - break; - case "q": - doClick(document.getElementById("wmd-quote-button")); - break; - case "k": - doClick(document.getElementById("wmd-code-button")); - break; - case "g": - doClick(document.getElementById("wmd-image-button")); - break; - case "o": - doClick(document.getElementById("wmd-olist-button")); - break; - case "u": - doClick(document.getElementById("wmd-ulist-button")); - break; - case "h": - doClick(document.getElementById("wmd-heading-button")); - break; - case "r": - doClick(document.getElementById("wmd-hr-button")); - break; - case "y": - doClick(document.getElementById("wmd-redo-button")); - break; - case "z": - if(key.shiftKey) { - doClick(document.getElementById("wmd-redo-button")); - } - else { - doClick(document.getElementById("wmd-undo-button")); - } - break; - default: - return; - } - - - if (key.preventDefault) { - key.preventDefault(); - } - - if (window.event) { - window.event.returnValue = false; - } - } - }); - - // Auto-indent on shift-enter - util.addEvent(inputBox, "keyup", function(key){ - if (key.shiftKey && !key.ctrlKey && !key.metaKey) { - var keyCode = key.charCode || key.keyCode; - // Character 13 is Enter - if (keyCode === 13) { - fakeButton = {}; - fakeButton.textOp = command.doAutoindent; - doClick(fakeButton); - } - } - }); - - // special handler because IE clears the context of the textbox on ESC - if (global.isIE) { - util.addEvent(inputBox, "keydown", function(key){ - var code = key.keyCode; - if (code === 27) { - return false; - } - }); - } - - if (inputBox.form) { - var submitCallback = inputBox.form.onsubmit; - inputBox.form.onsubmit = function(){ - convertToHtml(); - if (submitCallback) { - return submitCallback.apply(this, arguments); - } - }; - } - }; - - // Convert the contents of the input textarea to HTML in the output/preview panels. - var convertToHtml = function(){ - - if (wmd.showdown) { - var markdownConverter = new wmd.showdown.converter(); - } - var text = inputBox.value; - - var callback = function(){ - inputBox.value = text; - }; - - if (!/markdown/.test(wmd.wmd_env.output.toLowerCase())) { - if (markdownConverter) { - inputBox.value = markdownConverter.makeHtml(text); - window.setTimeout(callback, 0); - } - } - return true; - }; - - - this.undo = function(){ - if (undoMgr) { - undoMgr.undo(); - } - }; - - this.redo = function(){ - if (undoMgr) { - undoMgr.redo(); - } - }; - - // This is pretty useless. The setupEditor function contents - // should just be copied here. - var init = function(){ - setupEditor(); - }; - - this.destroy = function(){ - if (undoMgr) { - undoMgr.destroy(); - } - if (div.parentNode) { - div.parentNode.removeChild(div); - } - if (inputBox) { - inputBox.style.marginTop = ""; - } - window.clearInterval(creationHandle); - }; - - init(); - }; - - // The input textarea state/contents. - // This is used to implement undo/redo by the undo manager. - wmd.TextareaState = function(){ - - // Aliases - var stateObj = this; - var inputArea = wmd.panels.input; - - this.init = function() { - - if (!util.isVisible(inputArea)) { - return; - } - - this.setInputAreaSelectionStartEnd(); - this.scrollTop = inputArea.scrollTop; - if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) { - this.text = inputArea.value; - } - - } - - // Sets the selected text in the input box after we've performed an - // operation. - this.setInputAreaSelection = function(){ - - if (!util.isVisible(inputArea)) { - return; - } - - if (inputArea.selectionStart !== undefined && !global.isOpera) { - - inputArea.focus(); - inputArea.selectionStart = stateObj.start; - inputArea.selectionEnd = stateObj.end; - inputArea.scrollTop = stateObj.scrollTop; - } - else if (doc.selection) { - - if (doc.activeElement && doc.activeElement !== inputArea) { - return; - } - - inputArea.focus(); - var range = inputArea.createTextRange(); - range.moveStart("character", -inputArea.value.length); - range.moveEnd("character", -inputArea.value.length); - range.moveEnd("character", stateObj.end); - range.moveStart("character", stateObj.start); - range.select(); - } - }; - - this.setInputAreaSelectionStartEnd = function(){ - - if (inputArea.selectionStart || inputArea.selectionStart === 0) { - - stateObj.start = inputArea.selectionStart; - stateObj.end = inputArea.selectionEnd; - } - else if (doc.selection) { - - stateObj.text = util.fixEolChars(inputArea.value); - - // IE loses the selection in the textarea when buttons are - // clicked. On IE we cache the selection and set a flag - // which we check for here. - var range; - if(wmd.ieRetardedClick && wmd.ieCachedRange) { - range = wmd.ieCachedRange; - wmd.ieRetardedClick = false; - } - else { - range = doc.selection.createRange(); - } - - var fixedRange = util.fixEolChars(range.text); - var marker = "\x07"; - var markedRange = marker + fixedRange + marker; - range.text = markedRange; - var inputText = util.fixEolChars(inputArea.value); - - range.moveStart("character", -markedRange.length); - range.text = fixedRange; - - stateObj.start = inputText.indexOf(marker); - stateObj.end = inputText.lastIndexOf(marker) - marker.length; - - var len = stateObj.text.length - util.fixEolChars(inputArea.value).length; - - if (len) { - range.moveStart("character", -fixedRange.length); - while (len--) { - fixedRange += "\n"; - stateObj.end += 1; - } - range.text = fixedRange; - } - - this.setInputAreaSelection(); - } - }; - - // Restore this state into the input area. - this.restore = function(){ - - if (stateObj.text != undefined && stateObj.text != inputArea.value) { - inputArea.value = stateObj.text; - } - this.setInputAreaSelection(); - inputArea.scrollTop = stateObj.scrollTop; - }; - - // Gets a collection of HTML chunks from the inptut textarea. - this.getChunks = function(){ - - var chunk = new wmd.Chunks(); - - chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start)); - chunk.startTag = ""; - chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end)); - chunk.endTag = ""; - chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end)); - chunk.scrollTop = stateObj.scrollTop; - - return chunk; - }; - - // Sets the TextareaState properties given a chunk of markdown. - this.setChunks = function(chunk){ - - chunk.before = chunk.before + chunk.startTag; - chunk.after = chunk.endTag + chunk.after; - - if (global.isOpera) { - chunk.before = chunk.before.replace(/\n/g, "\r\n"); - chunk.selection = chunk.selection.replace(/\n/g, "\r\n"); - chunk.after = chunk.after.replace(/\n/g, "\r\n"); - } - - this.start = chunk.before.length; - this.end = chunk.before.length + chunk.selection.length; - this.text = chunk.before + chunk.selection + chunk.after; - this.scrollTop = chunk.scrollTop; - }; - - this.init(); - }; - - // before: contains all the text in the input box BEFORE the selection. - // after: contains all the text in the input box AFTER the selection. - wmd.Chunks = function(){ - }; - - // startRegex: a regular expression to find the start tag - // endRegex: a regular expresssion to find the end tag - wmd.Chunks.prototype.findTags = function(startRegex, endRegex){ - - var chunkObj = this; - var regex; - - if (startRegex) { - - regex = util.extendRegExp(startRegex, "", "$"); - - this.before = this.before.replace(regex, - function(match){ - chunkObj.startTag = chunkObj.startTag + match; - return ""; - }); - - regex = util.extendRegExp(startRegex, "^", ""); - - this.selection = this.selection.replace(regex, - function(match){ - chunkObj.startTag = chunkObj.startTag + match; - return ""; - }); - } - - if (endRegex) { - - regex = util.extendRegExp(endRegex, "", "$"); - - this.selection = this.selection.replace(regex, - function(match){ - chunkObj.endTag = match + chunkObj.endTag; - return ""; - }); - - regex = util.extendRegExp(endRegex, "^", ""); - - this.after = this.after.replace(regex, - function(match){ - chunkObj.endTag = match + chunkObj.endTag; - return ""; - }); - } - }; - - // If remove is false, the whitespace is transferred - // to the before/after regions. - // - // If remove is true, the whitespace disappears. - wmd.Chunks.prototype.trimWhitespace = function(remove){ - - this.selection = this.selection.replace(/^(\s*)/, ""); - - if (!remove) { - this.before += re.$1; - } - - this.selection = this.selection.replace(/(\s*)$/, ""); - - if (!remove) { - this.after = re.$1 + this.after; - } - }; - - - wmd.Chunks.prototype.skipLines = function(nLinesBefore, nLinesAfter, findExtraNewlines){ - - if (nLinesBefore === undefined) { - nLinesBefore = 1; - } - - if (nLinesAfter === undefined) { - nLinesAfter = 1; - } - - nLinesBefore++; - nLinesAfter++; - - var regexText; - var replacementText; - - this.selection = this.selection.replace(/(^\n*)/, ""); - this.startTag = this.startTag + re.$1; - this.selection = this.selection.replace(/(\n*$)/, ""); - this.endTag = this.endTag + re.$1; - this.startTag = this.startTag.replace(/(^\n*)/, ""); - this.before = this.before + re.$1; - this.endTag = this.endTag.replace(/(\n*$)/, ""); - this.after = this.after + re.$1; - - if (this.before) { - - regexText = replacementText = ""; - - while (nLinesBefore--) { - regexText += "\\n?"; - replacementText += "\n"; - } - - if (findExtraNewlines) { - regexText = "\\n*"; - } - this.before = this.before.replace(new re(regexText + "$", ""), replacementText); - } - - if (this.after) { - - regexText = replacementText = ""; - - while (nLinesAfter--) { - regexText += "\\n?"; - replacementText += "\n"; - } - if (findExtraNewlines) { - regexText = "\\n*"; - } - - this.after = this.after.replace(new re(regexText, ""), replacementText); - } - }; - - // The markdown symbols - 4 spaces = code, > = blockquote, etc. - command.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)"; - - // Remove markdown symbols from the chunk selection. - command.unwrap = function(chunk){ - var txt = new re("([^\\n])\\n(?!(\\n|" + command.prefixes + "))", "g"); - chunk.selection = chunk.selection.replace(txt, "$1 $2"); - }; - - command.wrap = function(chunk, len){ - command.unwrap(chunk); - var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"); - - chunk.selection = chunk.selection.replace(regex, function(line, marked){ - if (new re("^" + command.prefixes, "").test(line)) { - return line; - } - return marked + "\n"; - }); - - chunk.selection = chunk.selection.replace(/\s+$/, ""); - }; - - command.doBold = function(chunk, postProcessing){ - return command.doBorI(chunk, postProcessing, 2, "strong text"); - }; - - command.doItalic = function(chunk, postProcessing){ - return command.doBorI(chunk, postProcessing, 1, "emphasized text"); - }; - - // chunk: The selected region that will be enclosed with */** - // nStars: 1 for italics, 2 for bold - // insertText: If you just click the button without highlighting text, this gets inserted - command.doBorI = function(chunk, postProcessing, nStars, insertText){ - - // Get rid of whitespace and fixup newlines. - chunk.trimWhitespace(); - chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n"); - - // Look for stars before and after. Is the chunk already marked up? - chunk.before.search(/(\**$)/); - var starsBefore = re.$1; - - chunk.after.search(/(^\**)/); - var starsAfter = re.$1; - - var prevStars = Math.min(starsBefore.length, starsAfter.length); - - // Remove stars if we have to since the button acts as a toggle. - if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) { - chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), ""); - chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), ""); - } - else if (!chunk.selection && starsAfter) { - // It's not really clear why this code is necessary. It just moves - // some arbitrary stuff around. - chunk.after = chunk.after.replace(/^([*_]*)/, ""); - chunk.before = chunk.before.replace(/(\s?)$/, ""); - var whitespace = re.$1; - chunk.before = chunk.before + starsAfter + whitespace; - } - else { - - // In most cases, if you don't have any selected text and click the button - // you'll get a selected, marked up region with the default text inserted. - if (!chunk.selection && !starsAfter) { - chunk.selection = insertText; - } - - // Add the true markup. - var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ? - chunk.before = chunk.before + markup; - chunk.after = markup + chunk.after; - } - - return; - }; - - command.stripLinkDefs = function(text, defsToAdd){ - - text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, - function(totalMatch, id, link, newlines, title){ - defsToAdd[id] = totalMatch.replace(/\s*$/, ""); - if (newlines) { - // Strip the title and return that separately. - defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, ""); - return newlines + title; - } - return ""; - }); - - return text; - }; - - command.addLinkDef = function(chunk, linkDef){ - - var refNumber = 0; // The current reference number - var defsToAdd = {}; // - // Start with a clean slate by removing all previous link definitions. - chunk.before = command.stripLinkDefs(chunk.before, defsToAdd); - chunk.selection = command.stripLinkDefs(chunk.selection, defsToAdd); - chunk.after = command.stripLinkDefs(chunk.after, defsToAdd); - - var defs = ""; - var regex = /(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g; - - var addDefNumber = function(def){ - refNumber++; - def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:"); - defs += "\n" + def; - }; - - var getLink = function(wholeMatch, link, id, end){ - - if (defsToAdd[id]) { - addDefNumber(defsToAdd[id]); - return link + refNumber + end; - - } - return wholeMatch; - }; - - chunk.before = chunk.before.replace(regex, getLink); - - if (linkDef) { - addDefNumber(linkDef); - } - else { - chunk.selection = chunk.selection.replace(regex, getLink); - } - - var refOut = refNumber; - - chunk.after = chunk.after.replace(regex, getLink); - - if (chunk.after) { - chunk.after = chunk.after.replace(/\n*$/, ""); - } - if (!chunk.after) { - chunk.selection = chunk.selection.replace(/\n*$/, ""); - } - - chunk.after += "\n\n" + defs; - - return refOut; - }; - - command.doLinkOrImage = function(chunk, postProcessing, isImage){ - - chunk.trimWhitespace(); - chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/); - - if (chunk.endTag.length > 1) { - - chunk.startTag = chunk.startTag.replace(/!?\[/, ""); - chunk.endTag = ""; - command.addLinkDef(chunk, null); - - } - else { - - if (/\n\n/.test(chunk.selection)) { - command.addLinkDef(chunk, null); - return; - } - - // The function to be executed when you enter a link and press OK or Cancel. - // Marks up the link and adds the ref. - var makeLinkMarkdown = function(link){ - - if (link !== null) { - - chunk.startTag = chunk.endTag = ""; - // sanitize the link - link = encodeURI(link); - link = link.split("/").slice(0,3).join("/") + "/" + link.split("/").slice(3).join("/").replace(/:/g,"%3A"); - - var linkDef = " [999]: " + link; - - var num = command.addLinkDef(chunk, linkDef); - chunk.startTag = isImage ? "![" : "["; - chunk.endTag = "][" + num + "]"; - - if (!chunk.selection) { - if (isImage) { - chunk.selection = "alt text"; - } - else { - chunk.selection = "link text"; - } - } - } - postProcessing(); - }; - - if (isImage) { - util.prompt(imageDialogText, imageDefaultText, makeLinkMarkdown); - } - else { - util.prompt(linkDialogText, linkDefaultText, makeLinkMarkdown); - } - return true; - } - }; - - util.makeAPI = function(){ - wmd.wmd = {}; - wmd.wmd.editor = wmd.editor; - wmd.wmd.previewManager = wmd.previewManager; - }; - - util.startEditor = function(){ - - var edit; // The editor (buttons + input + outputs) - the main object. - // Anton: I'm hijacking previewMgr in footer.js so I can trigger a refresh - //var previewMgr; // The preview manager. - - wmd.panels = new wmd.PanelCollection(); - - previewMgr = new wmd.previewManager(); - var previewRefreshCallback = previewMgr.refresh; - - edit = new wmd.editor(previewRefreshCallback); - previewMgr.refresh(true); - }; - - wmd.previewManager = function(){ - var mathjaxRunning = false; // true when MathJax is processing - mathjaxDelay = 0; - - var managerObj = this; - var converter; - var poller; - var timeout; - var elapsedTime; - var agingInputText; // input text which produced the output one step back - var oldInputText; // input text which produced the output two steps back - var htmlOut; - var maxDelay = 3000; - var startType = "delayed"; // The other legal value is "manual" - - // Adds event listeners to elements and creates the input poller. - var setupEvents = function(inputElem, listener){ - - util.addEvent(inputElem, "input", listener); - inputElem.onpaste = listener; - inputElem.ondrop = listener; - - util.addEvent(inputElem, "keypress", listener); - util.addEvent(inputElem, "keydown", listener); - }; - - var getDocScrollTop = function(){ - - var result = 0; - - if (window.innerHeight) { - result = window.pageYOffset; - } - else - if (doc.documentElement && doc.documentElement.scrollTop) { - result = doc.documentElement.scrollTop; - } - else - if (doc.body) { - result = doc.body.scrollTop; - } - - return result; - }; - - var makePreviewHtml = function(){ - if (mathjaxRunning) { - return; - } - - // If there are no registered preview and output panels - // there is nothing to do. - if (!wmd.panels.preview && !wmd.panels.output) { - return; - } - - var text = wmd.panels.input.value; - if (text && text == oldInputText) { - return; // Input text hasn't changed. - } - else { - oldInputText = agingInputText - agingInputText = text; - } - - var prevTime = new Date().getTime(); - - if (!converter && wmd.showdown) { - converter = new wmd.showdown.converter(); - } - - if (converter) { - text = converter.makeHtml(text); - } - - // Calculate the processing time of the HTML creation. - // It's used as the delay time in the event listener. - var currTime = new Date().getTime(); - elapsedTime = currTime - prevTime; - - pushPreviewHtml(text); - htmlOut = text; - }; - - // setTimeout is already used. Used as an event listener. - var applyTimeout = function(){ - - if (timeout) { - window.clearTimeout(timeout); - timeout = undefined; - } - - if (startType !== "manual") { - - var delay = 0; - - if (startType === "delayed") { - delay = elapsedTime + mathjaxDelay; - } - - if (delay > maxDelay) { - delay = maxDelay; - } - timeout = window.setTimeout(makePreviewHtml, delay); - } - }; - - var getScaleFactor = function(panel){ - if (panel.scrollHeight <= panel.clientHeight) { - return 1; - } - return panel.scrollTop / (panel.scrollHeight - panel.clientHeight); - }; - - var setPanelScrollTops = function(){ - - if (wmd.panels.preview) { - wmd.panels.preview.scrollTop = (wmd.panels.preview.scrollHeight - wmd.panels.preview.clientHeight) * getScaleFactor(wmd.panels.preview); - ; - } - - if (wmd.panels.output) { - wmd.panels.output.scrollTop = (wmd.panels.output.scrollHeight - wmd.panels.output.clientHeight) * getScaleFactor(wmd.panels.output); - ; - } - }; - - this.refresh = function(requiresRefresh){ - - if (requiresRefresh) { - oldInputText = ""; - makePreviewHtml(); - } - else { - applyTimeout(); - } - }; - - this.processingTime = function(){ - return elapsedTime + mathjaxDelay; - }; - - // The output HTML - this.output = function(){ - return htmlOut; - }; - - var isFirstTimeFilled = true; - - var sanitizeHtml = function (html) { - return html.replace(/<[^<>]*>?/gi, sanitizeTag); - } - - // (tags that can be opened/closed) | (tags that stand alone) - var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i; - //
          | - var a_white = /^(]+")?\s?>|<\/a>)$/i; - - // ]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i; - - function sanitizeTag(tag) - { - if(tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white)) - return tag; - else - return ""; - } - - var pushPreviewHtml = function(text) { - - var emptyTop = position.getTop(wmd.panels.input) - getDocScrollTop(); - - // Send the encoded HTML to the output textarea/div. - if (wmd.panels.output) { - // The value property is only defined if the output is a textarea. - if (wmd.panels.output.value !== undefined) { - wmd.panels.output.value = text; - wmd.panels.output.readOnly = true; - } - // Otherwise we are just replacing the text in a div. - // Send the HTML wrapped in
          
          -				else {
          -					var newText = text.replace(/&/g, "&").replace(/
          "; - } - } - - if (wmd.panels.preview && wmd.panels.buffer) { - if (mathPreview && MathJax) { - wmd.panels.buffer.innerHTML = sanitizeHtml(text); - mathjaxRunning = true; - var prevTime = new Date().getTime(); - MathJax.Hub.Queue(["Typeset", MathJax.Hub, wmd.panels.buffer], function () { - wmd.panels.SwapBuffers(); - mathjaxRunning = false; - var currTime = new Date().getTime(); - mathjaxDelay = currTime - prevTime; - }); - } else { - mathjaxDelay = 0; - wmd.panels.preview.innerHTML = sanitizeHtml(text); - } - } - - setPanelScrollTops(); - - if (isFirstTimeFilled) { - isFirstTimeFilled = false; - return; - } - - var fullTop = position.getTop(wmd.panels.input) - getDocScrollTop(); - - if (global.isIE) { - window.setTimeout(function(){ - window.scrollBy(0, fullTop - emptyTop); - }, 0); - } - else { - window.scrollBy(0, fullTop - emptyTop); - } - }; - - var init = function(){ - - setupEvents(wmd.panels.input, applyTimeout); - makePreviewHtml(); - - if (wmd.panels.preview) { - wmd.panels.preview.scrollTop = 0; - } - if (wmd.panels.output) { - wmd.panels.output.scrollTop = 0; - } - }; - - this.destroy = function(){ - if (poller) { - poller.destroy(); - } - }; - - init(); - }; - - // When making a list, hitting shift-enter will put your cursor on the next line - // at the current indent level. - command.doAutoindent = function(chunk, postProcessing){ - - chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n"); - chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n"); - chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n"); - - if(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)){ - if(command.doList){ - command.doList(chunk); - } - } - if(/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)){ - if(command.doBlockquote){ - command.doBlockquote(chunk); - } - } - if(/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)){ - if(command.doCode){ - command.doCode(chunk); - } - } - }; - - command.doBlockquote = function(chunk, postProcessing){ - - chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/, - function(totalMatch, newlinesBefore, text, newlinesAfter){ - chunk.before += newlinesBefore; - chunk.after = newlinesAfter + chunk.after; - return text; - }); - - chunk.before = chunk.before.replace(/(>[ \t]*)$/, - function(totalMatch, blankLine){ - chunk.selection = blankLine + chunk.selection; - return ""; - }); - - chunk.selection = chunk.selection.replace(/^(\s|>)+$/ ,""); - chunk.selection = chunk.selection || "Blockquote"; - - if(chunk.before){ - chunk.before = chunk.before.replace(/\n?$/,"\n"); - } - if(chunk.after){ - chunk.after = chunk.after.replace(/^\n?/,"\n"); - } - - chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/, - function(totalMatch){ - chunk.startTag = totalMatch; - return ""; - }); - - chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/, - function(totalMatch){ - chunk.endTag = totalMatch; - return ""; - }); - - var replaceBlanksInTags = function(useBracket){ - - var replacement = useBracket ? "> " : ""; - - if(chunk.startTag){ - chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/, - function(totalMatch, markdown){ - return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; - }); - } - if(chunk.endTag){ - chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/, - function(totalMatch, markdown){ - return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; - }); - } - }; - - if(/^(?![ ]{0,3}>)/m.test(chunk.selection)){ - command.wrap(chunk, wmd.wmd_env.lineLength - 2); - chunk.selection = chunk.selection.replace(/^/gm, "> "); - replaceBlanksInTags(true); - chunk.skipLines(); - } - else{ - chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, ""); - command.unwrap(chunk); - replaceBlanksInTags(false); - - if(!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag){ - chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n"); - } - - if(!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag){ - chunk.endTag=chunk.endTag.replace(/^\n{0,2}/, "\n\n"); - } - } - - if(!/\n/.test(chunk.selection)){ - chunk.selection = chunk.selection.replace(/^(> *)/, - function(wholeMatch, blanks){ - chunk.startTag += blanks; - return ""; - }); - } - }; - - command.doCode = function(chunk, postProcessing){ - - var hasTextBefore = /\S[ ]*$/.test(chunk.before); - var hasTextAfter = /^[ ]*\S/.test(chunk.after); - - // Use 'four space' markdown if the selection is on its own - // line or is multiline. - if((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)){ - - chunk.before = chunk.before.replace(/[ ]{4}$/, - function(totalMatch){ - chunk.selection = totalMatch + chunk.selection; - return ""; - }); - - var nLinesBack = 1; - var nLinesForward = 1; - - if(/\n(\t|[ ]{4,}).*\n$/.test(chunk.before)){ - nLinesBack = 0; - } - if(/^\n(\t|[ ]{4,})/.test(chunk.after)){ - nLinesForward = 0; - } - - chunk.skipLines(nLinesBack, nLinesForward); - - if(!chunk.selection){ - chunk.startTag = " "; - chunk.selection = "enter code here"; - } - else { - if(/^[ ]{0,3}\S/m.test(chunk.selection)){ - chunk.selection = chunk.selection.replace(/^/gm, " "); - } - else{ - chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, ""); - } - } - } - else{ - // Use backticks (`) to delimit the code block. - - chunk.trimWhitespace(); - chunk.findTags(/`/, /`/); - - if(!chunk.startTag && !chunk.endTag){ - chunk.startTag = chunk.endTag="`"; - if(!chunk.selection){ - chunk.selection = "enter code here"; - } - } - else if(chunk.endTag && !chunk.startTag){ - chunk.before += chunk.endTag; - chunk.endTag = ""; - } - else{ - chunk.startTag = chunk.endTag=""; - } - } - }; - - command.doList = function(chunk, postProcessing, isNumberedList){ - - // These are identical except at the very beginning and end. - // Should probably use the regex extension function to make this clearer. - var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/; - var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/; - - // The default bullet is a dash but others are possible. - // This has nothing to do with the particular HTML bullet, - // it's just a markdown bullet. - var bullet = "-"; - - // The number in a numbered list. - var num = 1; - - // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list. - var getItemPrefix = function(){ - var prefix; - if(isNumberedList){ - prefix = " " + num + ". "; - num++; - } - else{ - prefix = " " + bullet + " "; - } - return prefix; - }; - - // Fixes the prefixes of the other list items. - var getPrefixedItem = function(itemText){ - - // The numbering flag is unset when called by autoindent. - if(isNumberedList === undefined){ - isNumberedList = /^\s*\d/.test(itemText); - } - - // Renumber/bullet the list element. - itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm, - function( _ ){ - return getItemPrefix(); - }); - - return itemText; - }; - - chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null); - - if(chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)){ - chunk.before += chunk.startTag; - chunk.startTag = ""; - } - - if(chunk.startTag){ - - var hasDigits = /\d+[.]/.test(chunk.startTag); - chunk.startTag = ""; - chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n"); - command.unwrap(chunk); - chunk.skipLines(); - - if(hasDigits){ - // Have to renumber the bullet points if this is a numbered list. - chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem); - } - if(isNumberedList == hasDigits){ - return; - } - } - - var nLinesUp = 1; - - chunk.before = chunk.before.replace(previousItemsRegex, - function(itemText){ - if(/^\s*([*+-])/.test(itemText)){ - bullet = re.$1; - } - nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; - return getPrefixedItem(itemText); - }); - - if(!chunk.selection){ - chunk.selection = "List item"; - } - - var prefix = getItemPrefix(); - - var nLinesDown = 1; - - chunk.after = chunk.after.replace(nextItemsRegex, - function(itemText){ - nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; - return getPrefixedItem(itemText); - }); - - chunk.trimWhitespace(true); - chunk.skipLines(nLinesUp, nLinesDown, true); - chunk.startTag = prefix; - var spaces = prefix.replace(/./g, " "); - command.wrap(chunk, wmd.wmd_env.lineLength - spaces.length); - chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces); - - }; - - command.doHeading = function(chunk, postProcessing){ - - // Remove leading/trailing whitespace and reduce internal spaces to single spaces. - chunk.selection = chunk.selection.replace(/\s+/g, " "); - chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, ""); - - // If we clicked the button with no selected text, we just - // make a level 2 hash header around some default text. - if(!chunk.selection){ - chunk.startTag = "## "; - chunk.selection = "Heading"; - chunk.endTag = " ##"; - return; - } - - var headerLevel = 0; // The existing header level of the selected text. - - // Remove any existing hash heading markdown and save the header level. - chunk.findTags(/#+[ ]*/, /[ ]*#+/); - if(/#+/.test(chunk.startTag)){ - headerLevel = re.lastMatch.length; - } - chunk.startTag = chunk.endTag = ""; - - // Try to get the current header level by looking for - and = in the line - // below the selection. - chunk.findTags(null, /\s?(-+|=+)/); - if(/=+/.test(chunk.endTag)){ - headerLevel = 1; - } - if(/-+/.test(chunk.endTag)){ - headerLevel = 2; - } - - // Skip to the next line so we can create the header markdown. - chunk.startTag = chunk.endTag = ""; - chunk.skipLines(1, 1); - - // We make a level 2 header if there is no current header. - // If there is a header level, we substract one from the header level. - // If it's already a level 1 header, it's removed. - var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1; - - if(headerLevelToCreate > 0){ - - // The button only creates level 1 and 2 underline headers. - // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner? - var headerChar = headerLevelToCreate >= 2 ? "-" : "="; - var len = chunk.selection.length; - if(len > wmd.wmd_env.lineLength){ - len = wmd.wmd_env.lineLength; - } - chunk.endTag = "\n"; - while(len--){ - chunk.endTag += headerChar; - } - } - }; - - command.doHorizontalRule = function(chunk, postProcessing){ - chunk.startTag = "----------\n"; - chunk.selection = ""; - chunk.skipLines(2, 1, true); - } -}; - -// create button-row and bind functions, plus cruft because neutering original Attacklab doesn't always work right -setTimeout('if ($("#show-editor-button").length == 0){Attacklab.wmdBase();Attacklab.Util.startEditor();if ($("#wmd-button-bar>*").length>1){$("#wmd-button-row:first").hide();}} primeMJ();', 500); - diff --git a/common/static/js/vendor/jquery.ajaxfileupload.js b/common/static/js/vendor/jquery.ajaxfileupload.js new file mode 100644 index 0000000000..d761c5ae3c --- /dev/null +++ b/common/static/js/vendor/jquery.ajaxfileupload.js @@ -0,0 +1,206 @@ +jQuery.extend({ + handleError: function( s, xhr, status, e ) { +// If a local callback was specified, fire it + if ( s.error ) { + s.error.call( s.context || s, xhr, status, e ); + } + + // Fire the global callback + if ( s.global ) { + (s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [xhr, s, e] ); + } + }, + createUploadIframe: function(id, uri){ + //create frame + var frameId = 'jUploadFrame' + id; + if(window.ActiveXObject) { + var io = document.createElement(' +