Commit 62fa253a by E. Kolpakov Committed by Jonathan Piacenti

Backported edx-platform hosted discussion XBlock to edx-solutions (part 2)

parent 8c934284
...@@ -55,14 +55,7 @@ class DiscussionXBlock(XBlock): ...@@ -55,14 +55,7 @@ class DiscussionXBlock(XBlock):
""" """
:return: int course id :return: int course id
""" """
# TODO really implement this return unicode(self.location.course_key)
# pylint: disable=no-member
if hasattr(self, 'xmodule_runtime'):
if hasattr(self.xmodule_runtime.course_id, 'to_deprecated_string'):
return self.xmodule_runtime.course_id.to_deprecated_string()
else:
return self.xmodule_runtime.course_id
return 'foo'
def student_view(self, context=None): # pylint: disable=unused-argument def student_view(self, context=None): # pylint: disable=unused-argument
""" Renders student view for LMS and Studio """ """ Renders student view for LMS and Studio """
...@@ -78,7 +71,7 @@ class DiscussionXBlock(XBlock): ...@@ -78,7 +71,7 @@ class DiscussionXBlock(XBlock):
""" Renders student view for LMS """ """ Renders student view for LMS """
fragment = Fragment() fragment = Fragment()
discussion_service = self.xmodule_runtime.service(self, 'discussion') # pylint: disable=no-member discussion_service = self.xmodule_runtime.service(self, 'discussion') # pylint: disable=no-member
context = discussion_service.get_inline_template_context(self.discussion_id) context = discussion_service.get_inline_template_context()
context['discussion_id'] = self.discussion_id context['discussion_id'] = self.discussion_id
fragment.add_content(render_mako_template('discussion/_discussion_inline.html', context)) fragment.add_content(render_mako_template('discussion/_discussion_inline.html', context))
...@@ -174,6 +167,12 @@ class DiscussionCourseXBlock(XBlock): ...@@ -174,6 +167,12 @@ class DiscussionCourseXBlock(XBlock):
context = discussion_service.get_course_template_context() context = discussion_service.get_course_template_context()
context['enable_new_post_btn'] = True context['enable_new_post_btn'] = True
for url in get_js_urls():
fragment.add_javascript_url(url)
for url in get_css_urls():
fragment.add_css_url(url)
fragment.add_content(render_mako_template('discussion/_discussion_course.html', context)) fragment.add_content(render_mako_template('discussion/_discussion_course.html', context))
fragment.add_javascript(render_template('static/js/discussion_course.js', { fragment.add_javascript(render_template('static/js/discussion_course.js', {
...@@ -182,12 +181,6 @@ class DiscussionCourseXBlock(XBlock): ...@@ -182,12 +181,6 @@ class DiscussionCourseXBlock(XBlock):
fragment.add_content(render_mustache_templates()) fragment.add_content(render_mustache_templates())
for url in get_js_urls():
fragment.add_javascript_url(url)
for url in get_css_urls():
fragment.add_css_url(url)
fragment.initialize_js('DiscussionCourseBlock') fragment.initialize_js('DiscussionCourseBlock')
return fragment return fragment
......
...@@ -5,7 +5,7 @@ function DiscussionInlineBlock(runtime, element) { ...@@ -5,7 +5,7 @@ function DiscussionInlineBlock(runtime, element) {
var testUrl = runtime.handlerUrl(element, 'test'); var testUrl = runtime.handlerUrl(element, 'test');
if (testUrl.match(/^(http|https):\/\//)) { if (testUrl.match(/^(http|https):\/\//)) {
var hostname = testUrl.match(/^(.*:\/\/[a-z\-.]+)\//)[1]; var hostname = testUrl.match(/^(.*:\/\/[a-z0-9:\-.]+)\//)[1];
DiscussionUtil.setBaseUrl(hostname); DiscussionUtil.setBaseUrl(hostname);
} }
......
...@@ -9,6 +9,7 @@ from django.conf import settings ...@@ -9,6 +9,7 @@ from django.conf import settings
from mako.template import Template as MakoTemplate from mako.template import Template as MakoTemplate
JS_URLS = [ JS_URLS = [
# VENDOR # VENDOR
'js/vendor/URI.min.js', 'js/vendor/URI.min.js',
...@@ -17,6 +18,7 @@ JS_URLS = [ ...@@ -17,6 +18,7 @@ JS_URLS = [
'js/vendor/underscore-min.js', 'js/vendor/underscore-min.js',
'js/vendor/backbone-min.js', 'js/vendor/backbone-min.js',
'js/vendor/mustache.js', 'js/vendor/mustache.js',
'js/vendor/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-MML-AM_HTMLorMML-full',
'xblock/discussion/js/vendor/split.js', 'xblock/discussion/js/vendor/split.js',
'xblock/discussion/js/vendor/i18n.js', 'xblock/discussion/js/vendor/i18n.js',
...@@ -28,10 +30,16 @@ JS_URLS = [ ...@@ -28,10 +30,16 @@ JS_URLS = [
] ]
CSS_URLS = [ CSS_URLS = [
# 'xblock/discussion/css/discussion-app.css', 'xblock/discussion/css/vendor/font-awesome.css',
'xblock/discussion/css/vendor/font-awesome.css' 'sass/discussion-forum.css',
] ]
main_js = u'coffee/src/discussion/main.js'
all_js = set(rooted_glob(settings.COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js'))
all_js.remove(main_js)
discussion_js = sorted(all_js) + [main_js]
def load_resource(resource_path): def load_resource(resource_path):
""" """
...@@ -47,7 +55,7 @@ def render_template(template_path, context=None): ...@@ -47,7 +55,7 @@ def render_template(template_path, context=None):
""" """
template_str = load_resource(template_path) template_str = load_resource(template_path)
template = Template(template_str) template = Template(template_str)
return template.render(Context(context if context else {})) return template.render(Context(context or {}))
def render_mako_template(template_path, context=None): def render_mako_template(template_path, context=None):
...@@ -67,7 +75,7 @@ def render_mustache_templates(): ...@@ -67,7 +75,7 @@ def render_mustache_templates():
def read_file(file_name): def read_file(file_name):
""" Reads file and decodes it's content """ """ Reads file and decodes it's content """
return open(mustache_dir + '/' + file_name, "r").read().decode('utf-8') return (mustache_dir / file_name).text("utf-8")
def template_id_from_file_name(file_name): def template_id_from_file_name(file_name):
""" Generates template_id from file name """ """ Generates template_id from file name """
...@@ -121,7 +129,7 @@ def load_scenarios_from_path(scenarios_path): ...@@ -121,7 +129,7 @@ def load_scenarios_from_path(scenarios_path):
def get_js_urls(): def get_js_urls():
""" Returns a list of all additional javascript files """ """ Returns a list of all additional javascript files """
return [asset_to_static_url(path) for path in JS_URLS] return [asset_to_static_url(path) for path in JS_URLS + discussion_js]
def get_css_urls(): def get_css_urls():
......
...@@ -1585,14 +1585,15 @@ class DiscussionService(object): ...@@ -1585,14 +1585,15 @@ class DiscussionService(object):
Returns the context to render the course-level discussion templates. Returns the context to render the course-level discussion templates.
""" """
# for some reason pylint reports courseware.access, courseware.courses and django_comment_client.forum.views
# pylint: disable=import-error
import json import json
from django.http import HttpRequest from django.http import HttpRequest
import lms.lib.comment_client as cc import lms.lib.comment_client as cc
from courseware.access import has_access from courseware.access import has_access
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from django_comment_client.forum.views import get_threads
from django_comment_client.permissions import has_permission from django_comment_client.permissions import has_permission
from django_comment_client.forum.views import get_threads, make_course_settings
import django_comment_client.utils as utils import django_comment_client.utils as utils
from openedx.core.djangoapps.course_groups.cohorts import ( from openedx.core.djangoapps.course_groups.cohorts import (
is_course_cohorted, is_course_cohorted,
...@@ -1623,6 +1624,8 @@ class DiscussionService(object): ...@@ -1623,6 +1624,8 @@ class DiscussionService(object):
cohorts = get_course_cohorts(course_id) cohorts = get_course_cohorts(course_id)
cohorted_commentables = get_cohorted_commentables(course_id) cohorted_commentables = get_cohorted_commentables(course_id)
course_settings = make_course_settings(course)
context = { context = {
'course': course, 'course': course,
'course_id': course_id, 'course_id': course_id,
...@@ -1637,22 +1640,25 @@ class DiscussionService(object): ...@@ -1637,22 +1640,25 @@ class DiscussionService(object):
'is_moderator': has_permission(user, "see_all_cohorts", course_id), 'is_moderator': has_permission(user, "see_all_cohorts", course_id),
'cohorts': cohorts, 'cohorts': cohorts,
'user_cohort': user_cohort_id, 'user_cohort': user_cohort_id,
'sort_preference': user_info['default_sort_key'],
'cohorted_commentables': cohorted_commentables, 'cohorted_commentables': cohorted_commentables,
'is_course_cohorted': is_course_cohorted(course_id), 'is_course_cohorted': is_course_cohorted(course_id),
'has_permission_to_create_thread': has_permission(user, "create_thread", course_id), 'has_permission_to_create_thread': has_permission(user, "create_thread", course_id),
'has_permission_to_create_comment': has_permission(user, "create_comment", course_id), 'has_permission_to_create_comment': has_permission(user, "create_comment", course_id),
'has_permission_to_create_subcomment': has_permission(user, "create_subcomment", course_id), 'has_permission_to_create_subcomment': has_permission(user, "create_subcomment", course_id),
'has_permission_to_openclose_thread': has_permission(user, "openclose_thread", course_id) 'has_permission_to_openclose_thread': has_permission(user, "openclose_thread", course_id),
'course_settings': saxutils.escape(json.dumps(course_settings), escapedict),
} }
return context return context
def get_inline_template_context(self, discussion_id): def get_inline_template_context(self):
""" """
Returns the context to render inline discussion templates. Returns the context to render inline discussion templates.
""" """
# for some reason pylint reports courseware.access, courseware.courses and django_comment_client.forum.views
import lms.lib.comment_client as cc # pylint: disable=import-error
from django.conf import settings
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from courseware.access import has_access from courseware.access import has_access
from django_comment_client.permissions import has_permission from django_comment_client.permissions import has_permission
...@@ -1669,6 +1675,8 @@ class DiscussionService(object): ...@@ -1669,6 +1675,8 @@ class DiscussionService(object):
has_access(user, 'staff', course) has_access(user, 'staff', course)
context = { context = {
'user': user,
'settings': settings,
'course': course, 'course': course,
'category_map': category_map, 'category_map': category_map,
'is_moderator': is_moderator, 'is_moderator': is_moderator,
...@@ -1682,7 +1690,7 @@ class DiscussionService(object): ...@@ -1682,7 +1690,7 @@ class DiscussionService(object):
return context return context
class ModuleSystem(MetricsMixin,ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method
""" """
This is an abstraction such that x_modules can function independent This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS, of the courseware (e.g. import into other types of courseware, LMS,
......
...@@ -20,7 +20,9 @@ class @DiscussionSpecHelper ...@@ -20,7 +20,9 @@ class @DiscussionSpecHelper
) )
@makeEventSpy = () -> @makeEventSpy = () ->
jasmine.createSpyObj('event', ['preventDefault', 'target']) obj = jasmine.createSpyObj('event', ['preventDefault'])
obj.target = document.createElement('div');
obj
@makeCourseSettings = (is_cohorted=true) -> @makeCourseSettings = (is_cohorted=true) ->
new DiscussionCourseSettings( new DiscussionCourseSettings(
......
...@@ -96,7 +96,9 @@ describe "NewPostView", -> ...@@ -96,7 +96,9 @@ describe "NewPostView", ->
@view.render() @view.render()
expectedGroupId = null expectedGroupId = null
DiscussionSpecHelper.makeAjaxSpy( DiscussionSpecHelper.makeAjaxSpy(
(params) -> expect(params.data.group_id).toEqual(expectedGroupId) (params) =>
expect(params.data.group_id).toEqual(expectedGroupId)
@view.$(".forum-new-post-form").removeAttr("disabled")
) )
_.each( _.each(
......
...@@ -13,11 +13,15 @@ $ -> ...@@ -13,11 +13,15 @@ $ ->
class @DiscussionUtil class @DiscussionUtil
@baseUrl = '';
@wmdEditors: {} @wmdEditors: {}
@getTemplate: (id) -> @getTemplate: (id) ->
$("script##{id}").html() $("script##{id}").html()
@setBaseUrl: (baseUrl) ->
@baseUrl = baseUrl
@setUser: (user) -> @setUser: (user) ->
@user = user @user = user
...@@ -54,7 +58,7 @@ class @DiscussionUtil ...@@ -54,7 +58,7 @@ class @DiscussionUtil
.click -> handler(this) .click -> handler(this)
@urlFor: (name, param, param1, param2) -> @urlFor: (name, param, param1, param2) ->
{ @baseUrl + {
follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow" follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow"
unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow" unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow"
create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create" create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create"
...@@ -141,7 +145,14 @@ class @DiscussionUtil ...@@ -141,7 +145,14 @@ class @DiscussionUtil
return deferred.promise() return deferred.promise()
params["url"] = URI(params["url"]).addSearch ajax: 1 params["url"] = URI(params["url"]).addSearch ajax: 1
params["beforeSend"] = -> if !params["error"]
params["error"] = =>
@discussionAlert(
gettext("Sorry"),
gettext("We had some trouble processing your request. Please ensure you have copied any unsaved work and then reload the page.")
)
# important difference - can't use beforeSend as it's used in jquery.xblock to set up xhr parameters
if $elem if $elem
$elem.attr("disabled", "disabled") $elem.attr("disabled", "disabled")
if params["$loading"] if params["$loading"]
...@@ -149,12 +160,7 @@ class @DiscussionUtil ...@@ -149,12 +160,7 @@ class @DiscussionUtil
params["loadingCallback"].apply(params["$loading"]) params["loadingCallback"].apply(params["$loading"])
else else
params["$loading"].loading(params["takeFocus"]) params["$loading"].loading(params["takeFocus"])
if !params["error"]
params["error"] = =>
@discussionAlert(
gettext("Sorry"),
gettext("We had some trouble processing your request. Please ensure you have copied any unsaved work and then reload the page.")
)
request = $.ajax(params).always -> request = $.ajax(params).always ->
if $elem if $elem
$elem.removeAttr("disabled") $elem.removeAttr("disabled")
......
../../templates/js/discussion
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* This file is dynamically generated and ignored by Git.
* DO NOT MAKE CHANGES HERE. Instead, go edit its template:
* /edx/app/edxapp/edx-platform/lms/static/sass/discussion-forum.scss.mako
*/
// lms - css application architecture (platform)
// ====================
// libs and resets *do not edit*
@import 'bourbon/bourbon'; // lib - bourbon
// BASE *default edX offerings*
// ====================
// base - utilities
@import 'base/variables';
@import 'base/mixins';
// base - assets
@import 'base/font_face';
@import 'base/extends';
// base - elements
@import 'elements/typography';
@import 'elements/controls';
// shared
@import 'shared/modal';
// applications
@import "discussion/utilities/variables";
@import "discussion/mixins";
@import 'discussion/discussion'; // Process old file after definitions but before everything else
@import "discussion/elements/actions";
@import "discussion/elements/editor";
@import "discussion/elements/labels";
@import "discussion/elements/navigation";
@import "discussion/views/thread";
@import "discussion/views/create-edit-post";
@import "discussion/views/response";
@import 'discussion/utilities/developer';
@import 'discussion/utilities/shame';
## NOTE: This Sass infrastructure is redundant, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx
// lms - css application architecture (platform)
// ====================
// libs and resets *do not edit*
@import 'bourbon/bourbon'; // lib - bourbon
// BASE *default edX offerings*
// ====================
// base - utilities
@import 'base/variables';
@import 'base/mixins';
## THEMING
## -------
## Set up this file to import an edX theme library if the environment
## indicates that a theme should be used. The assumption is that the
## theme resides outside of this main edX repository, in a directory
## called themes/<theme-name>/, with its base Sass file in
## themes/<theme-name>/static/sass/_<theme-name>.scss. That one entry
## point can be used to @import in as many other things as needed.
% if env["FEATURES"].get("USE_CUSTOM_THEME", False):
// import theme's Sass overrides
@import '${env.get('THEME_NAME')}';
% endif
// base - assets
@import 'base/font_face';
@import 'base/extends';
// base - elements
@import 'elements/typography';
@import 'elements/controls';
// shared
@import 'shared/modal';
// applications
@import "discussion/utilities/variables";
@import "discussion/mixins";
@import 'discussion/discussion'; // Process old file after definitions but before everything else
@import "discussion/elements/actions";
@import "discussion/elements/editor";
@import "discussion/elements/labels";
@import "discussion/elements/navigation";
@import "discussion/views/thread";
@import "discussion/views/create-edit-post";
@import "discussion/views/response";
@import 'discussion/utilities/developer';
@import 'discussion/utilities/shame';
// discussion - elements - labels // discussion - elements - labels
// ==================== // ====================
body.discussion, .discussion-module, .discussion-body { body.discussion, .discussion-module, .discussion-body, .discussion-course {
.post-label-pinned { .post-label-pinned {
@include forum-post-label($forum-color-pinned); @include forum-post-label($forum-color-pinned);
} }
......
...@@ -8,6 +8,31 @@ ...@@ -8,6 +8,31 @@
width: 31%; width: 31%;
border: 1px solid #aaa; border: 1px solid #aaa;
border-radius: 3px; border-radius: 3px;
div.discussion-course, div.content-wrapper {
section.discussion {
div.forum-nav {
@include box-sizing(border-box);
float: left;
position: relative;
width: 31%;
border: 1px solid #aaa;
border-radius: 3px;
ul, ol {
list-style: none;
padding-left: 0;
li.forum-nav-thread {
margin-bottom: 0;
}
&.forum-nav-browse-submenu {
padding-left: $baseline;
list-style: none;
}
}
}
}
} }
// ------ // ------
...@@ -99,10 +124,7 @@ ...@@ -99,10 +124,7 @@
list-style: none; list-style: none;
} }
.forum-nav-browse-submenu {
padding-left: $baseline;
list-style: none;
}
.forum-nav-browse-title { .forum-nav-browse-title {
display: block; display: block;
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
// provisional styling for "search alerts" (messages boxes that appear in the sidebar below the search // provisional styling for "search alerts" (messages boxes that appear in the sidebar below the search
// input field with notices pertaining to the search result). // input field with notices pertaining to the search result).
// -------------------- // --------------------
body.discussion { body.discussion, .discussion-course, div.discussion-body {
.forum-nav { .forum-nav {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// UI: form structure // UI: form structure
.forum-new-post-form, .forum-new-post-form,
.edit-post-form { .edit-post-form {
@include clearfix(); @include clearfix;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
border-radius: 3px; border-radius: 3px;
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
.field-help { .field-help {
@include box-sizing(border-box); @include box-sizing(border-box);
display: inline-block; display: inline-block;
@include padding-left($baseline); padding-left: ($baseline);
width: 50%; width: 50%;
font-size: 12px; font-size: 12px;
} }
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
@include white-button; @include white-button;
@extend %cont-truncated; @extend %cont-truncated;
z-index: 1000; z-index: 1000;
padding: 0 $baseline 0 ($baseline*0.75); padding: 0 $baseline 0 ($baseline*.75);
height: 40px; height: 40px;
font-size: 14px; font-size: 14px;
line-height: 36px; line-height: 36px;
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
line-height: 36px; line-height: 36px;
.icon { .icon {
margin-right: ($baseline/4); margin-right: 5px;
} }
} }
...@@ -131,7 +131,7 @@ ...@@ -131,7 +131,7 @@
.post-option { .post-option {
@include box-sizing(border-box); @include box-sizing(border-box);
display: inline-block; display: inline-block;
@include margin-right($baseline); margin-right: ($baseline);
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 3px; border-radius: 3px;
padding: ($baseline/2); padding: ($baseline/2);
...@@ -208,10 +208,10 @@ ...@@ -208,10 +208,10 @@
.topic-menu-wrapper { .topic-menu-wrapper {
@include box-sizing(border-box); @include box-sizing(border-box);
@extend %ui-depth4;
position: absolute; position: absolute;
top: 40px; top: 40px;
left: 0; left: 0;
z-index: 9999;
border: 1px solid $gray-l3; border: 1px solid $gray-l3;
width: 100%; width: 100%;
background: $white; background: $white;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// ==================== // ====================
// general thread layout // general thread layout
body.discussion, .discussion-module { body.discussion, .discussion-module, div.discussion-course {
// post layout // post layout
.discussion-post { .discussion-post {
...@@ -124,7 +124,7 @@ body.discussion, .discussion-module { ...@@ -124,7 +124,7 @@ body.discussion, .discussion-module {
background-color: $white; background-color: $white;
} }
body.discussion, .discussion-thread.expanded { body.discussion, .discussion-thread.expanded, div.discussion-course {
.forum-thread-main-wrapper { .forum-thread-main-wrapper {
box-shadow: 0 1px 3px $shadow; box-shadow: 0 1px 3px $shadow;
} }
......
...@@ -44,6 +44,8 @@ ${page_title_breadcrumbs(course_name())} ...@@ -44,6 +44,8 @@ ${page_title_breadcrumbs(course_name())}
<%block name="nav_skip">${"#seq_content" if section_title else "#course-content"}</%block> <%block name="nav_skip">${"#seq_content" if section_title else "#course-content"}</%block>
<%include file="../discussion/_js_head_dependencies.html" />
% if show_chat: % if show_chat:
<link rel="stylesheet" href="${static.url('css/vendor/ui-lightness/jquery-ui-1.8.22.custom.css')}" /> <link rel="stylesheet" href="${static.url('css/vendor/ui-lightness/jquery-ui-1.8.22.custom.css')}" />
## It'd be better to have this in a place like lms/css/vendor/candy, ## It'd be better to have this in a place like lms/css/vendor/candy,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment