Commit a9f74b94 by E. Kolpakov

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

parent 102fb523
......@@ -55,14 +55,7 @@ class DiscussionXBlock(XBlock):
"""
:return: int course id
"""
# TODO really implement this
# 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'
return unicode(self.location.course_key)
def student_view(self, context=None): # pylint: disable=unused-argument
""" Renders student view for LMS and Studio """
......@@ -78,7 +71,7 @@ class DiscussionXBlock(XBlock):
""" Renders student view for LMS """
fragment = Fragment()
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
fragment.add_content(render_mako_template('discussion/_discussion_inline.html', context))
......@@ -174,6 +167,12 @@ class DiscussionCourseXBlock(XBlock):
context = discussion_service.get_course_template_context()
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_javascript(render_template('static/js/discussion_course.js', {
......@@ -182,12 +181,6 @@ class DiscussionCourseXBlock(XBlock):
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')
return fragment
......
......@@ -5,7 +5,7 @@ function DiscussionInlineBlock(runtime, element) {
var testUrl = runtime.handlerUrl(element, 'test');
if (testUrl.match(/^(http|https):\/\//)) {
var hostname = testUrl.match(/^(.*:\/\/[a-z\-.]+)\//)[1];
var hostname = testUrl.match(/^(.*:\/\/[a-z0-9:\-.]+)\//)[1];
DiscussionUtil.setBaseUrl(hostname);
}
......
......@@ -9,6 +9,8 @@ from django.conf import settings
from mako.template import Template as MakoTemplate
from rooted_paths import rooted_glob
JS_URLS = [
# VENDOR
'js/vendor/URI.min.js',
......@@ -17,6 +19,7 @@ JS_URLS = [
'js/vendor/underscore-min.js',
'js/vendor/backbone-min.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/i18n.js',
......@@ -28,10 +31,16 @@ JS_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):
"""
......@@ -47,7 +56,7 @@ def render_template(template_path, context=None):
"""
template_str = load_resource(template_path)
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):
......@@ -67,7 +76,7 @@ def render_mustache_templates():
def read_file(file_name):
""" 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):
""" Generates template_id from file name """
......@@ -121,7 +130,7 @@ def load_scenarios_from_path(scenarios_path):
def get_js_urls():
""" 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():
......
......@@ -1254,13 +1254,14 @@ class DiscussionService(object):
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
from django.http import HttpRequest
import lms.lib.comment_client as cc
from courseware.access import has_access
from courseware.courses import get_course_with_access
from django_comment_client.forum.views import get_threads
from django_comment_client.forum.views import get_threads, make_course_settings
from django_comment_client.permissions import cached_has_permission
import django_comment_client.utils as utils
from course_groups.cohorts import (
......@@ -1273,7 +1274,7 @@ class DiscussionService(object):
escapedict = {'"': '"'}
request = HttpRequest()
user = self.runtime.user
user = self.runtime.user
request.user = user
user_info = cc.User.from_django_user(self.runtime.user).to_dict()
course_id = self.runtime.course_id
......@@ -1283,15 +1284,13 @@ class DiscussionService(object):
unsafethreads, query_params = get_threads(request, course_id)
threads = [utils.safe_content(thread, course_id) for thread in unsafethreads]
flag_moderator = cached_has_permission(user, 'openclose_thread', course_id) or \
has_access(user, 'staff', course)
flag_moderator = cached_has_permission(user, 'openclose_thread', course_id) or has_access(user, 'staff', course)
annotated_content_info = utils.get_metadata_for_threads(course_id, threads, user, user_info)
category_map = utils.get_discussion_category_map(course)
cohorts = get_course_cohorts(course_id)
cohorted_commentables = get_cohorted_commentables(course_id)
course_settings = make_course_settings(course)
context = {
'course': course,
'course_id': course_id,
......@@ -1301,13 +1300,15 @@ class DiscussionService(object):
'user_info': saxutils.escape(json.dumps(user_info), escapedict),
'flag_moderator': flag_moderator,
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
'category_map': category_map,
'category_map': course_settings['category_map'],
'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict),
'is_moderator': cached_has_permission(user, "see_all_cohorts", course_id),
'cohorts': cohorts,
'cohorts': course_settings['cohorts'],
'user_cohort': user_cohort_id,
'sort_preference': user_info['default_sort_key'],
'cohorted_commentables': cohorted_commentables,
'is_course_cohorted': is_course_cohorted(course_id),
'is_course_cohorted': course_settings['is_cohorted'],
'course_settings': saxutils.escape(json.dumps(course_settings), escapedict),
'has_permission_to_create_thread': cached_has_permission(user, "create_thread", course_id),
'has_permission_to_create_comment': cached_has_permission(user, "create_comment", course_id),
'has_permission_to_create_subcomment': cached_has_permission(user, "create_subcomment", course_id),
......@@ -1316,12 +1317,13 @@ class DiscussionService(object):
return context
def get_inline_template_context(self, discussion_id):
def get_inline_template_context(self):
"""
Returns the context to render inline discussion templates.
"""
import lms.lib.comment_client as cc
# for some reason pylint reports courseware.access, courseware.courses and django_comment_client.forum.views
# pylint: disable=import-error
from django.conf import settings
from courseware.courses import get_course_with_access
from courseware.access import has_access
from django_comment_client.permissions import cached_has_permission
......@@ -1334,10 +1336,11 @@ class DiscussionService(object):
category_map = get_discussion_category_map(course)
is_moderator = cached_has_permission(user, "see_all_cohorts", course_id)
flag_moderator = cached_has_permission(user, 'openclose_thread', course_id) or \
has_access(user, 'staff', course)
flag_moderator = cached_has_permission(user, 'openclose_thread', course_id) or has_access(user, 'staff', course)
context = {
'user': user,
'settings': settings,
'course': course,
'category_map': category_map,
'is_moderator': is_moderator,
......@@ -1351,7 +1354,7 @@ class DiscussionService(object):
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
of the courseware (e.g. import into other types of courseware, LMS,
......
......@@ -20,7 +20,9 @@ class @DiscussionSpecHelper
)
@makeEventSpy = () ->
jasmine.createSpyObj('event', ['preventDefault', 'target'])
obj = jasmine.createSpyObj('event', ['preventDefault'])
obj.target = document.createElement('div');
obj
@makeCourseSettings = (is_cohorted=true) ->
new DiscussionCourseSettings(
......
......@@ -26,16 +26,16 @@
};
});
testUpdate = function(view, thread) {
testUpdate = function(view, thread, newTopicId, newTopicName) {
spyOn($, 'ajax').andCallFake(function(params) {
expect(params.url.path()).toEqual(DiscussionUtil.urlFor('update_thread', 'dummy_id'));
expect(params.data.thread_type).toBe('discussion');
expect(params.data.commentable_id).toBe('other_topic');
expect(params.data.commentable_id).toBe(newTopicId);
expect(params.data.title).toBe('changed thread title');
params.success();
return {always: function() {}};
});
view.$el.find('a.topic-title')[1].click(); // set new topic
view.$el.find('a.topic-title[data-discussion-id="'+newTopicId+'"]').click(); // set new topic
view.$('.edit-post-title').val('changed thread title'); // set new title
view.$("label[for$='post-type-discussion']").click(); // set new thread type
view.$('.post-update').click();
......@@ -43,20 +43,20 @@
expect(thread.get('title')).toBe('changed thread title');
expect(thread.get('thread_type')).toBe('discussion');
expect(thread.get('commentable_id')).toBe('other_topic');
expect(thread.get('courseware_title')).toBe('Other Topic');
expect(thread.get('commentable_id')).toBe(newTopicId);
expect(thread.get('courseware_title')).toBe(newTopicName);
expect(view.$('.edit-post-title')).toHaveValue('');
expect(view.$('.wmd-preview p')).toHaveText('');
};
it('can save new data correctly in tab mode', function() {
this.createEditView();
testUpdate(this.view, this.thread);
testUpdate(this.view, this.thread, 'other_topic', 'Other Topic');
});
it('can save new data correctly in inline mode', function() {
this.createEditView({"mode": "inline"});
testUpdate(this.view, this.thread);
testUpdate(this.view, this.thread, 'other_topic', 'Other Topic');
});
testCancel = function(view) {
......@@ -73,5 +73,45 @@
this.createEditView({"mode": "inline"});
testCancel(this.view);
});
describe('renderComments', function() {
beforeEach(function() {
this.course_settings = new DiscussionCourseSettings({
'category_map': {
'children': ['Topic', 'General', 'Basic Question'],
'entries': {
'Topic': {
'is_cohorted': true,
'id': 'topic'
},
"General": {
"sort_key": "General",
"is_cohorted": false,
"id": "6.00.1x_General"
},
"Basic Question": {
"is_cohorted": false,
"id": "6>00'1x\"Basic_Question"
}
}
},
'is_cohorted': true
});
});
it('can save new data correctly for current discussion id without dots', function () {
this.createEditView({topicId: "topic"});
testUpdate(this.view, this.thread, "6.00.1x_General", "General");
});
it('can save new data correctly for current discussion id with dots', function () {
this.createEditView({topicId: "6.00.1x_General"});
testUpdate(this.view, this.thread, "6>00'1x\"Basic_Question", "Basic Question");
});
it('can save new data correctly for current discussion id with special characters', function () {
this.createEditView({topicId: "6>00'1x\"Basic_Question"});
testUpdate(this.view, this.thread, "6.00.1x_General", "General");
});
});
});
}).call(this);
......@@ -54,7 +54,9 @@ describe "NewPostView", ->
@view.render()
expectedGroupId = null
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(
......
......@@ -13,11 +13,15 @@ $ ->
class @DiscussionUtil
@baseUrl = '';
@wmdEditors: {}
@getTemplate: (id) ->
$("script##{id}").html()
@setBaseUrl: (baseUrl) ->
@baseUrl = baseUrl
@setUser: (user) ->
@user = user
......@@ -54,7 +58,7 @@ class @DiscussionUtil
.click -> handler(this)
@urlFor: (name, param, param1, param2) ->
{
@baseUrl + {
follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow"
unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow"
create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create"
......@@ -137,20 +141,22 @@ class @DiscussionUtil
if $elem and $elem.attr("disabled")
return
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"]
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
$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 ->
if $elem
$elem.removeAttr("disabled")
......
......@@ -79,4 +79,5 @@ spec_paths:
fixture_paths:
- js/fixtures
- js/capa/fixtures
- templates/discussion
../../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.
......@@ -56,7 +56,7 @@
@import "discussion/elements/labels";
@import "discussion/elements/navigation";
@import "discussion/views/thread";
@import "discussion/views/new-post";
@import "discussion/views/create-edit-post";
@import "discussion/views/response";
@import 'discussion/utilities/developer';
@import 'discussion/utilities/shame';
......
/*
* 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
// ====================
body.discussion, .discussion-module, .discussion-body {
body.discussion, .discussion-module, .discussion-body, .discussion-course {
.post-label-pinned {
@include forum-post-label($forum-color-pinned);
}
......
// discussion - elements - navigation
// ====================
.forum-nav {
@include box-sizing(border-box);
float: left;
position: relative;
width: 31%;
border: 1px solid #aaa;
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 +117,7 @@
list-style: none;
}
.forum-nav-browse-submenu {
padding-left: $baseline;
list-style: none;
}
.forum-nav-browse-title {
display: block;
......
......@@ -17,7 +17,7 @@
// provisional styling for "search alerts" (messages boxes that appear in the sidebar below the search
// input field with notices pertaining to the search result).
// --------------------
body.discussion {
body.discussion, .discussion-course, div.discussion-body {
.forum-nav {
......
......@@ -107,7 +107,8 @@ li[class*=forum-nav-thread-label-] {
// new post form
// -------------
.forum-new-post-form {
.forum-new-post-form,
.edit-post-form {
// Override global label rules
.post-type {
text-shadow: none;
......@@ -127,7 +128,7 @@ li[class*=forum-nav-thread-label-] {
margin-bottom: 0;
}
// Override global span rules
// Override global span rules
.post-topic-button .drop-arrow {
line-height: 36px;
}
......
......@@ -2,7 +2,8 @@
// ====================
// UI: form structure
.forum-new-post-form {
.forum-new-post-form,
.edit-post-form {
@include clearfix;
box-sizing: border-box;
margin: 0;
......@@ -45,7 +46,7 @@
.field-help {
@include box-sizing(border-box);
display: inline-block;
padding-left: $baseline;
padding-left: ($baseline);
width: 50%;
font-size: 12px;
}
......@@ -64,7 +65,8 @@
// ====================
// UI: inputs
.forum-new-post-form {
.forum-new-post-form,
.edit-post-form {
.post-topic-button {
@include white-button;
@extend %cont-truncated;
......@@ -129,7 +131,7 @@
.post-option {
@include box-sizing(border-box);
display: inline-block;
margin-right: $baseline;
margin-right: ($baseline);
border: 1px solid transparent;
border-radius: 3px;
padding: ($baseline/2);
......@@ -172,7 +174,8 @@
// ====================
// UI: errors - new post creation
.forum-new-post-form {
.forum-new-post-form,
.edit-post-form {
.post-errors {
margin-bottom: $baseline;
border-radius: 3px;
......@@ -199,7 +202,8 @@
// UI: topic menu
// TO-DO: refactor to use _navigation.scss as general topic selector
.forum-new-post-form .post-topic {
.forum-new-post-form .post-topic ,
.edit-post-form .post-topic {
position: relative;
.topic-menu-wrapper {
......
......@@ -2,7 +2,7 @@
// ====================
// general thread layout
body.discussion, .discussion-module {
body.discussion, .discussion-module, div.discussion-course {
// post layout
.discussion-post {
......@@ -122,7 +122,7 @@ body.discussion, .discussion-module {
background-color: $white;
}
body.discussion, .discussion-thread.expanded {
body.discussion, .discussion-thread.expanded, div.discussion-course {
.forum-thread-main-wrapper {
box-shadow: 0 1px 3px $shadow;
}
......
......@@ -30,6 +30,8 @@ ${page_title_breadcrumbs(course_name())}
<%block name="nav_skip">${"#seq_content" if section_title else "#course-content"}</%block>
<%include file="../discussion/_js_head_dependencies.html" />
% if show_chat:
<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,
......
......@@ -7,5 +7,4 @@
-e git+https://github.com/edx-solutions/xblock-drag-and-drop-v2.git@4482aa60643288943123fe589fab14a7f60cdc69#egg=xblock-drag-and-drop-v2
-e git+https://github.com/edx-solutions/xblock-ooyala.git@81f91e6b03a50907e6838f0494b0bcf14e968a5e#egg=xblock-ooyala
-e git+https://github.com/edx-solutions/xblock-group-project.git@a659bc6582c91f063a053919ebe8113afab0cafd#egg=xblock-group-project
-e git+https://github.com/edx-solutions/xblock-discussion.git@139206cc5eee6bbf5bbd50e358d4a2c9261ca834#egg=xblock-discussion
-e git+https://github.com/edx-solutions/xblock-adventure.git@30894de41ee7efe7d5b6400086463d12995893f7#egg=xblock-adventure
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