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,
...@@ -1604,7 +1605,7 @@ class DiscussionService(object): ...@@ -1604,7 +1605,7 @@ class DiscussionService(object):
escapedict = {'"': '"'} escapedict = {'"': '"'}
request = HttpRequest() request = HttpRequest()
user = self.runtime.user user = self.runtime.user
request.user = user request.user = user
user_info = cc.User.from_django_user(self.runtime.user).to_dict() user_info = cc.User.from_django_user(self.runtime.user).to_dict()
course_id = self.runtime.course_id course_id = self.runtime.course_id
...@@ -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,20 +145,22 @@ class @DiscussionUtil ...@@ -141,20 +145,22 @@ 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 $elem
$elem.attr("disabled", "disabled")
if params["$loading"]
if params["loadingCallback"]?
params["loadingCallback"].apply(params["$loading"])
else
params["$loading"].loading(params["takeFocus"])
if !params["error"] if !params["error"]
params["error"] = => params["error"] = =>
@discussionAlert( @discussionAlert(
gettext("Sorry"), gettext("Sorry"),
gettext("We had some trouble processing your request. Please ensure you have copied any unsaved work and then reload the page.") 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
$elem.attr("disabled", "disabled")
if params["$loading"]
if params["loadingCallback"]?
params["loadingCallback"].apply(params["$loading"])
else
params["$loading"].loading(params["takeFocus"])
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