Commit e0c7228d by E. Kolpakov

Quality, safe template linter and test fixes:

* Safe template linter reports zero violations on modified files
* Removed some (or most) coffeescript-genereated returns
* Big HTML chunks in JS converted to underscore templates
* Markdown processor - safe tempate linter compatibility
* Fixed double escaping HTML tags in code blocks caused by passing it through postMathJaxProcessor twice
parent 8d1e10bd
...@@ -93,20 +93,17 @@ ...@@ -93,20 +93,17 @@
DiscussionRouter.prototype.allThreads = function() { DiscussionRouter.prototype.allThreads = function() {
this.nav.updateSidebar(); this.nav.updateSidebar();
return this.nav.goHome(); this.nav.goHome();
}; };
DiscussionRouter.prototype.setActiveThread = function() { DiscussionRouter.prototype.setActiveThread = function() {
if (this.thread) { if (this.thread) {
return this.nav.setActiveThread(this.thread.get("id")); this.nav.setActiveThread(this.thread.get("id"));
} else {
return this.nav.goHome();
} }
}; };
DiscussionRouter.prototype.showThread = function(forum_name, thread_id) { DiscussionRouter.prototype.showThread = function(forum_name, thread_id) {
var self; var self = this;
self = this;
this.thread = this.discussion.get(thread_id); this.thread = this.discussion.get(thread_id);
if (this.thread) { if (this.thread) {
...@@ -124,11 +121,13 @@ ...@@ -124,11 +121,13 @@
self.renderThreadView(); self.renderThreadView();
}).fail(function(xhr) { }).fail(function(xhr) {
// otherwise display error message and navigate to all threads view // otherwise display error message and navigate to all threads view
var errorMessage = (xhr.status === 404) ? var errorMsg;
gettext("The thread you selected has been deleted. Please select another thread.") : if (xhr.status === 404) {
gettext("We had some trouble loading more responses. Please try again."); errorMsg = gettext("The thread you selected has been deleted. Please select another thread.");
} else {
DiscussionUtil.discussionAlert(gettext("Sorry"), errorMessage); errorMsg = gettext("We had some trouble loading more responses. Please try again.");
}
DiscussionUtil.discussionAlert(gettext("Sorry"), errorMsg);
this.allThreads(); this.allThreads();
}); });
}; };
...@@ -162,23 +161,23 @@ ...@@ -162,23 +161,23 @@
this.main.on("thread:responses:rendered", function() { this.main.on("thread:responses:rendered", function() {
return self.nav.updateSidebar(); return self.nav.updateSidebar();
}); });
return this.thread.on("thread:thread_type_updated", this.showMain); this.thread.on("thread:thread_type_updated", this.showMain);
}; };
DiscussionRouter.prototype.navigateToThread = function(thread_id) { DiscussionRouter.prototype.navigateToThread = function(thread_id) {
var thread, targetThreadRoute; var thread, targetThreadRoute;
thread = this.discussion.get(thread_id); thread = this.discussion.get(thread_id);
targetThreadRoute = getSingleThreadRoute(thread.get("commentable_id"), thread_id); targetThreadRoute = getSingleThreadRoute(thread.get("commentable_id"), thread_id);
this.navigate(targetThreadRoute, { trigger: true}); this.navigate(targetThreadRoute, {trigger: true});
}; };
DiscussionRouter.prototype.navigateToAllThreads = function() { DiscussionRouter.prototype.navigateToAllThreads = function() {
this.navigate(allThreadsRoute, { trigger: true }); this.navigate(allThreadsRoute, {trigger: true});
}; };
DiscussionRouter.prototype.showNewPost = function() { DiscussionRouter.prototype.showNewPost = function() {
var self = this; var self = this;
return $('.forum-content').fadeOut({ $('.forum-content').fadeOut({
duration: 200, duration: 200,
complete: function() { complete: function() {
return self.newPost.fadeIn(200).focus(); return self.newPost.fadeIn(200).focus();
...@@ -187,7 +186,7 @@ ...@@ -187,7 +186,7 @@
}; };
DiscussionRouter.prototype.hideNewPost = function() { DiscussionRouter.prototype.hideNewPost = function() {
return this.newPost.fadeOut({ this.newPost.fadeOut({
duration: 200, duration: 200,
complete: function() { complete: function() {
return $('.forum-content').fadeIn(200).find('.thread-wrapper').focus(); return $('.forum-content').fadeIn(200).find('.thread-wrapper').focus();
......
/* globals $$course_id, Content, Markdown, URI */ /* globals $$course_id, Content, Markdown, MathJax, URI */
(function() { (function() {
'use strict'; 'use strict';
this.DiscussionUtil = (function() { this.DiscussionUtil = (function() {
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
DiscussionUtil.generateDiscussionLink = function(cls, txt, handler) { DiscussionUtil.generateDiscussionLink = function(cls, txt, handler) {
return $("<a>") return $("<a>")
.addClass("discussion-link").attr("href", "#") .addClass("discussion-link").attr("href", "#")
.addClass(cls).html(txt).click(function() {return handler(this);}); .addClass(cls).text(txt).click(function() {return handler(this);});
}; };
DiscussionUtil.urlFor = function(name, param, param1, param2) { DiscussionUtil.urlFor = function(name, param, param1, param2) {
...@@ -134,15 +134,17 @@ ...@@ -134,15 +134,17 @@
}; };
DiscussionUtil.showLoadingIndicator = function(element, takeFocus) { DiscussionUtil.showLoadingIndicator = function(element, takeFocus) {
this.$_loading = $( var animElem = edx.HtmlUtils.joinHtml(
"<div class='loading-animation' tabindex='0'><span class='sr'>" + edx.HtmlUtils.HTML("<div class='loading-animation' tabindex='0'><span class='sr'>"),
gettext("Loading content") + gettext("Loading content"),
"</span></div>" edx.HtmlUtils.HTML("</span></div>")
); );
element.after(this.$_loading); var $animElem = $(animElem.toString());
element.after($animElem);
this.$_loading = $animElem;
if (takeFocus) { if (takeFocus) {
this.makeFocusTrap(this.$_loading); this.makeFocusTrap(this.$_loading);
return this.$_loading.focus(); this.$_loading.focus();
} }
}; };
...@@ -151,40 +153,33 @@ ...@@ -151,40 +153,33 @@
}; };
DiscussionUtil.discussionAlert = function(header, body) { DiscussionUtil.discussionAlert = function(header, body) {
var alertDiv, alertTrigger; var $alertDiv, $alertTrigger;
// Prevents "text" is undefined in underscore.js in tests - looks like some tests use
// discussions somehow, but never append discussion fixtures or reset them; this causes
// entire test suite (lms, cms, common) to fail due to unhandled JS exception
var popupTemplate = $("#alert-popup").html() || "";
if ($("#discussion-alert").length === 0) { if ($("#discussion-alert").length === 0) {
alertDiv = $( $alertDiv = $(
"<div class='modal' role='alertdialog' id='discussion-alert' " + edx.HtmlUtils.template(popupTemplate)({}).toString()
"aria-describedby='discussion-alert-message'/>"
).css("display", "none");
alertDiv.html(
"<div class='inner-wrapper discussion-alert-wrapper'>" +
" <button class='close-modal dismiss' title='" + gettext("Close") + "'>" +
" <span class='icon fa fa-times' aria-hidden='true'></span>" +
" </button>" +
" <header><h2/><hr/></header>" +
" <p id='discussion-alert-message'/><hr/>" +
" <button class='dismiss'>" + gettext("OK") + "</button>" +
"</div>"
); );
this.makeFocusTrap(alertDiv.find("button")); this.makeFocusTrap($alertDiv.find("button"));
alertTrigger = $("<a href='#discussion-alert' id='discussion-alert-trigger'/>").css("display", "none"); $alertTrigger = $("<a href='#discussion-alert' id='discussion-alert-trigger'/>").css("display", "none");
alertTrigger.leanModal({ $alertTrigger.leanModal({
closeButton: "#discussion-alert .dismiss", closeButton: "#discussion-alert .dismiss",
overlay: 1, overlay: 1,
top: 200 top: 200
}); });
$("body").append(alertDiv).append(alertTrigger); $("body").append($alertDiv).append($alertTrigger);
} }
$("#discussion-alert header h2").html(header); $("#discussion-alert header h2").text(header);
$("#discussion-alert p").html(body); $("#discussion-alert p").text(body);
$("#discussion-alert-trigger").click(); $("#discussion-alert-trigger").click();
return $("#discussion-alert button").focus(); $("#discussion-alert button").focus();
}; };
DiscussionUtil.safeAjax = function(params) { DiscussionUtil.safeAjax = function(params) {
var $elem, deferred, request, var $elem, deferred, request,
self = this, deferred; self = this;
$elem = params.$elem; $elem = params.$elem;
if ($elem && $elem.prop("disabled")) { if ($elem && $elem.prop("disabled")) {
deferred = $.Deferred(); deferred = $.Deferred();
...@@ -212,7 +207,7 @@ ...@@ -212,7 +207,7 @@
if (params.loadingCallback) { if (params.loadingCallback) {
params.loadingCallback.apply(params.$loading); params.loadingCallback.apply(params.$loading);
} else { } else {
self.showLoadingIndicator($(params.$loading), params.takeFocus); self.showLoadingIndicator(params.$loading, params.takeFocus);
} }
} }
...@@ -266,9 +261,12 @@ ...@@ -266,9 +261,12 @@
DiscussionUtil.formErrorHandler = function(errorsField) { DiscussionUtil.formErrorHandler = function(errorsField) {
return function(xhr, textStatus, error) { return function(xhr, textStatus, error) {
var makeErrorElem, response, _i, _len, _ref, _results; var makeErrorElem, response, _i, _len, _ref, _results, $errorItem;
makeErrorElem = function(message) { makeErrorElem = function(message) {
return $("<li>").addClass("post-error").html(message); return edx.HtmlUtils.setHtml(
$("<li>").addClass("post-error"),
message
);
}; };
errorsField.empty().show(); errorsField.empty().show();
if (xhr.status === 400) { if (xhr.status === 400) {
...@@ -278,14 +276,16 @@ ...@@ -278,14 +276,16 @@
_results = []; _results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
error = _ref[_i]; error = _ref[_i];
_results.push(errorsField.append(makeErrorElem(error))); $errorItem = makeErrorElem(error);
_results.push(errorsField.append($errorItem));
} }
return _results; return _results;
} }
} else { } else {
return errorsField.append(makeErrorElem( $errorItem = makeErrorElem(
gettext("We had some trouble processing your request. Please try again.")) gettext("We had some trouble processing your request. Please try again.")
); );
return errorsField.append($errorItem);
} }
}; };
}; };
...@@ -294,11 +294,11 @@ ...@@ -294,11 +294,11 @@
return errorsField.empty(); return errorsField.empty();
}; };
DiscussionUtil.postMathJaxProcessor = function(text) { DiscussionUtil.postMathJaxProcessor = function(htmlSnippet) {
var RE_DISPLAYMATH, RE_INLINEMATH; var RE_DISPLAYMATH, RE_INLINEMATH;
RE_INLINEMATH = /^\$([^\$]*)\$/g; RE_INLINEMATH = /^\$([^\$]*)\$/g;
RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g; RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g;
return this.processEachMathAndCode(text, function(s, type) { return this.processEachMathAndCode(htmlSnippet, function(s, type) {
if (type === 'display') { if (type === 'display') {
return s.replace(RE_DISPLAYMATH, function($0, $1) { return s.replace(RE_DISPLAYMATH, function($0, $1) {
return "\\[" + $1 + "\\]"; return "\\[" + $1 + "\\]";
...@@ -356,84 +356,104 @@ ...@@ -356,84 +356,104 @@
return this.getWmdEditor($content, $local, cls_identifier).refreshPreview(); return this.getWmdEditor($content, $local, cls_identifier).refreshPreview();
}; };
DiscussionUtil.processEachMathAndCode = function(text, processor) { var RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m,
var $div, ESCAPED_BACKSLASH, ESCAPED_DOLLAR, RE_DISPLAYMATH, RE_INLINEMATH, cnt, codeArchive, processedText; RE_INLINEMATH = /^([^\$]*?)\$([^\$]+?)\$(.*)$/m,
codeArchive = []; ESCAPED_DOLLAR = '@@ESCAPED_D@@',
RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m;
RE_INLINEMATH = /^([^\$]*?)\$([^\$]+?)\$(.*)$/m;
ESCAPED_DOLLAR = '@@ESCAPED_D@@';
ESCAPED_BACKSLASH = '@@ESCAPED_B@@'; ESCAPED_BACKSLASH = '@@ESCAPED_B@@';
processedText = "";
$div = $("<div>").html(text); /**
* Formats math and code chunks
* @param htmlSnippet - post contents in form of safe (escaped and/or stripped) HTML
* @param processor - callback to post-process math and code chunks. Should return HtmlUtils.HTML or "subclass"
* @returns {*}
*/
DiscussionUtil.processEachMathAndCode = function(htmlSnippet, processor) {
var $div, codeArchive, processedHtmlString, htmlString;
codeArchive = {};
processedHtmlString = "";
$div = edx.HtmlUtils.setHtml($("<div>"), edx.HtmlUtils.ensureHtml(htmlSnippet));
$div.find("code").each(function(index, code) { $div.find("code").each(function(index, code) {
codeArchive.push($(code).html()); codeArchive[index] = $(code).html();
return $(code).html(codeArchive.length - 1); return $(code).text(index);
}); });
text = $div.html(); htmlString = $div.html();
text = text.replace(/\\\$/g, ESCAPED_DOLLAR); htmlString = htmlString.replace(/\\\$/g, ESCAPED_DOLLAR);
// suppressing Don't make functions within a loop. // suppressing Don't make functions within a loop.
/* jshint -W083 */ /* jshint -W083 */
while (true) { while (true) {
if (RE_INLINEMATH.test(text)) { if (RE_INLINEMATH.test(htmlString)) {
text = text.replace(RE_INLINEMATH, function($0, $1, $2, $3) { htmlString = htmlString.replace(RE_INLINEMATH, function($0, $1, $2, $3) {
processedText += $1 + processor("$" + $2 + "$", 'inline'); processedHtmlString += $1 + processor("$" + $2 + "$", 'inline');
return $3; return $3;
}); });
} else if (RE_DISPLAYMATH.test(text)) { } else if (RE_DISPLAYMATH.test(htmlString)) {
text = text.replace(RE_DISPLAYMATH, function($0, $1, $2, $3) { htmlString = htmlString.replace(RE_DISPLAYMATH, function($0, $1, $2, $3) {
/* /*
bug fix, ordering is off bug fix, ordering is off
*/ */
processedText = processor("$$" + $2 + "$$", 'display') + processedText; processedHtmlString = processor("$$" + $2 + "$$", 'display') + processedHtmlString;
processedText = $1 + processedText; processedHtmlString = $1 + processedHtmlString;
return $3; return $3;
}); });
} else { } else {
processedText += text; processedHtmlString += htmlString;
break; break;
} }
} }
/* jshint +W083 */ /* jshint +W083 */
text = processedText; htmlString = processedHtmlString;
text = text.replace(new RegExp(ESCAPED_DOLLAR, 'g'), '\\$'); htmlString = htmlString.replace(new RegExp(ESCAPED_DOLLAR, 'g'), '\\$');
text = text.replace(/\\\\\\\\/g, ESCAPED_BACKSLASH); htmlString = htmlString.replace(/\\\\\\\\/g, ESCAPED_BACKSLASH);
text = text.replace(/\\begin\{([a-z]*\*?)\}([\s\S]*?)\\end\{\1\}/img, function($0, $1, $2) { htmlString = htmlString.replace(/\\begin\{([a-z]*\*?)\}([\s\S]*?)\\end\{\1\}/img, function($0, $1, $2) {
return processor(("\\begin{" + $1 + "}") + $2 + ("\\end{" + $1 + "}")); return processor(("\\begin{" + $1 + "}") + $2 + ("\\end{" + $1 + "}"));
}); });
text = text.replace(new RegExp(ESCAPED_BACKSLASH, 'g'), '\\\\\\\\'); htmlString = htmlString.replace(new RegExp(ESCAPED_BACKSLASH, 'g'), '\\\\\\\\');
$div = $("<div>").html(text); $div = edx.HtmlUtils.setHtml($("<div>"), edx.HtmlUtils.HTML(htmlString));
cnt = 0;
$div.find("code").each(function(index, code) { $div.find("code").each(function(index, code) {
$(code).html(processor(codeArchive[cnt], 'code')); edx.HtmlUtils.setHtml(
return cnt += 1; $(code),
edx.HtmlUtils.HTML(processor(codeArchive[index], 'code'))
);
}); });
text = $div.html(); return edx.HtmlUtils.HTML($div.html());
return text;
}; };
DiscussionUtil.unescapeHighlightTag = function(text) { DiscussionUtil.unescapeHighlightTag = function(htmlSnippet) {
return text.replace( return edx.HtmlUtils.HTML(
/\&lt\;highlight\&gt\;/g, htmlSnippet.toString().replace(
"<span class='search-highlight'>").replace(/\&lt\;\/highlight\&gt\;/g, "</span>" /\&lt\;highlight\&gt\;/g,
"<span class='search-highlight'>").replace(/\&lt\;\/highlight\&gt\;/g, "</span>"
)
); );
}; };
DiscussionUtil.stripHighlight = function(text) { DiscussionUtil.stripHighlight = function(htmlString) {
return text.replace( return htmlString
/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, "").replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, "" .replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, "")
); .replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, "");
}; };
DiscussionUtil.stripLatexHighlight = function(text) { DiscussionUtil.stripLatexHighlight = function(htmlSnippet) {
return this.processEachMathAndCode(text, this.stripHighlight); return this.processEachMathAndCode(htmlSnippet, this.stripHighlight);
}; };
DiscussionUtil.markdownWithHighlight = function(text) { /**
* Processes markdown into formatted text and handles highlighting.
* @param unsafeText - raw markdown text, with all HTML entitites being *unescaped*.
* @returns HtmlSnippet
*/
DiscussionUtil.markdownWithHighlight = function(unsafeText) {
var converter; var converter;
text = text.replace(/^\&gt\;/gm, ">"); unsafeText = unsafeText.replace(/^\&gt\;/gm, ">");
converter = Markdown.getMathCompatibleConverter(); converter = Markdown.getMathCompatibleConverter();
text = this.unescapeHighlightTag(this.stripLatexHighlight(converter.makeHtml(text))); /*
return text.replace(/^>/gm, "&gt;"); * converter.makeHtml and HTML escaping:
* - converter.makeHtml is not HtmlSnippet aware, so we must pass unescaped raw text
* - converter.makeHtml strips html tags in post body and escapes in code blocks by design.
* HTML tags are not supported. Only markdown is supported.
*/
var htmlSnippet = edx.HtmlUtils.HTML(converter.makeHtml(unsafeText));
return this.unescapeHighlightTag(this.stripLatexHighlight(htmlSnippet));
}; };
DiscussionUtil.abbreviateString = function(text, minLength) { DiscussionUtil.abbreviateString = function(text, minLength) {
...@@ -447,19 +467,48 @@ ...@@ -447,19 +467,48 @@
} }
}; };
DiscussionUtil.abbreviateHTML = function(html, minLength) { DiscussionUtil.convertMath = function(element) {
edx.HtmlUtils.setHtml(
element,
this.postMathJaxProcessor(this.markdownWithHighlight(element.text()))
);
this.typesetMathJax(element);
};
DiscussionUtil.typesetMathJax = function(element) {
if (typeof MathJax !== "undefined" && MathJax !== null) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
};
DiscussionUtil.abbreviateHTML = function(htmlSnippet, maxLength) {
var $result, imagesToReplace, truncated_text; var $result, imagesToReplace, truncated_text;
truncated_text = jQuery.truncate(html, { truncated_text = edx.HtmlUtils.HTML(jQuery.truncate(htmlSnippet.toString(), {
length: minLength, length: maxLength,
noBreaks: true, noBreaks: true,
ellipsis: gettext('') ellipsis: gettext('')
}); }));
$result = $("<div>" + truncated_text + "</div>"); $result = $(edx.HtmlUtils.joinHtml(
edx.HtmlUtils.HTML("<div>"),
truncated_text,
edx.HtmlUtils.HTML("</div>")
).toString());
imagesToReplace = $result.find("img:not(:first)"); imagesToReplace = $result.find("img:not(:first)");
if (imagesToReplace.length > 0) { if (imagesToReplace.length > 0) {
$result.append("<p><em>Some images in this post have been omitted</em></p>"); edx.HtmlUtils.append(
$result,
edx.HtmlUtils.interpolateHtml(
edx.HtmlUtils.HTML("<p><em>{text}</em></p>"),
{text: gettext("Some images in this post have been omitted")}
)
);
} }
imagesToReplace.replaceWith("<em>image omitted</em>"); // See TNL-4983 for an explanation of why the linter requires ensureHtml()
var afterMessage = edx.HtmlUtils.interpolateHtml(
edx.HtmlUtils.HTML("<em>{text}</em>"), {text: gettext("image omitted")}
);
imagesToReplace.after(edx.HtmlUtils.ensureHtml(afterMessage).toString()).remove();
return $result.html(); return $result.html();
}; };
......
...@@ -271,20 +271,22 @@ ...@@ -271,20 +271,22 @@
return $button.toggleClass("is-checked", endorsed); return $button.toggleClass("is-checked", endorsed);
}, },
votes: function(votes) { votes: function(votes) {
var button, numVotes, selector, votesHtml, votesCountMsg; var button, numVotes, selector, votesText, votesCountMsg;
selector = ".action-vote"; selector = ".action-vote";
this.updateButtonState(selector, window.user.voted(this.model)); this.updateButtonState(selector, window.user.voted(this.model));
button = this.$el.find(selector); button = this.$el.find(selector);
numVotes = votes.up_count; numVotes = votes.up_count;
votesCountMsg = ngettext( votesCountMsg = ngettext(
"there is currently %(numVotes)s vote", "there are currently %(numVotes)s votes", numVotes "there is currently {numVotes} vote", "there are currently {numVotes} votes", numVotes
); );
button.find(".js-sr-vote-count").html(interpolate(votesCountMsg, {numVotes: numVotes }, true)); button.find(".js-sr-vote-count").empty().text(
votesHtml = interpolate(ngettext("%(numVotes)s Vote", "%(numVotes)s Votes", numVotes), { edx.StringUtils.interpolate(votesCountMsg, {numVotes: numVotes })
numVotes: numVotes );
}, true); votesText = edx.StringUtils.interpolate(
button.find(".vote-count").html(votesHtml); ngettext("{numVotes} Vote", "{numVotes} Votes", numVotes),
return this.$el.find('.display-vote .vote-count').html(votesHtml); { numVotes: numVotes });
button.find(".vote-count").empty().text(votesText);
this.$el.find('.display-vote .vote-count').empty().text(votesText);
}, },
pinned: function(pinned) { pinned: function(pinned) {
this.updateButtonState(".action-pin", pinned); this.updateButtonState(".action-pin", pinned);
......
...@@ -25,13 +25,14 @@ ...@@ -25,13 +25,14 @@
}, },
render: function() { render: function() {
var threadTypeTemplate, var formId = _.uniqueId("form-"),
formId = _.uniqueId("form-"); threadTypeTemplate = edx.HtmlUtils.template($("#thread-type-template").html()),
this.template = _.template($('#thread-edit-template').html()); $threadTypeSelector = $(threadTypeTemplate({form_id: formId}).toString()),
this.$el.html(this.template(this.model.toJSON())).appendTo(this.container); mainTemplate = edx.HtmlUtils.template($('#thread-edit-template').html());
this.submitBtn = this.$('.post-update'); edx.HtmlUtils.setHtml(this.$el, mainTemplate(this.model.toJSON()));
threadTypeTemplate = _.template($("#thread-type-template").html()); this.container.append(this.$el);
this.addField(threadTypeTemplate({form_id: formId})); this.$submitBtn = this.$('.post-update');
this.addField($threadTypeSelector);
this.$("#" + formId + "-post-type-" + this.threadType).attr('checked', true); this.$("#" + formId + "-post-type-" + this.threadType).attr('checked', true);
// Only allow the topic field for course threads, as standalone threads // Only allow the topic field for course threads, as standalone threads
// cannot be moved. // cannot be moved.
...@@ -46,8 +47,8 @@ ...@@ -46,8 +47,8 @@
return this; return this;
}, },
addField: function(fieldView) { addField: function($fieldView) {
this.$('.forum-edit-post-form-wrapper').append(fieldView); this.$('.forum-edit-post-form-wrapper').append($fieldView);
return this; return this;
}, },
...@@ -69,8 +70,8 @@ ...@@ -69,8 +70,8 @@
} }
return DiscussionUtil.safeAjax({ return DiscussionUtil.safeAjax({
$elem: this.submitBtn, $elem: this.$submitBtn,
$loading: this.submitBtn, $loading: this.$submitBtn,
url: DiscussionUtil.urlFor('update_thread', this.model.id), url: DiscussionUtil.urlFor('update_thread', this.model.id),
type: 'POST', type: 'POST',
dataType: 'json', dataType: 'json',
......
...@@ -134,12 +134,12 @@ ...@@ -134,12 +134,12 @@
}); });
this.searchAlertCollection.on("add", function(searchAlert) { this.searchAlertCollection.on("add", function(searchAlert) {
var content; var content;
content = _.template($("#search-alert-template").html())({ content = edx.HtmlUtils.template($("#search-alert-template").html())({
'message': searchAlert.attributes.message, 'messageHtml': searchAlert.attributes.message,
'cid': searchAlert.cid, 'cid': searchAlert.cid,
'css_class': searchAlert.attributes.css_class 'css_class': searchAlert.attributes.css_class
}); });
self.$(".search-alerts").append(content); edx.HtmlUtils.append(self.$(".search-alerts"), content);
return self.$("#search-alert-" + searchAlert.cid + " a.dismiss") return self.$("#search-alert-" + searchAlert.cid + " a.dismiss")
.bind("click", searchAlert, function(event) { .bind("click", searchAlert, function(event) {
return self.removeSearchAlert(event.data.cid); return self.removeSearchAlert(event.data.cid);
...@@ -179,13 +179,13 @@ ...@@ -179,13 +179,13 @@
}; };
DiscussionThreadListView.prototype.reloadDisplayedCollection = function(thread) { DiscussionThreadListView.prototype.reloadDisplayedCollection = function(thread) {
var active, content, current_el, thread_id; var active, $content, current_el, thread_id;
this.clearSearchAlerts(); this.clearSearchAlerts();
thread_id = thread.get('id'); thread_id = thread.get('id');
content = this.renderThread(thread); $content = this.renderThread(thread);
current_el = this.$(".forum-nav-thread[data-id=" + thread_id + "]"); current_el = this.$(".forum-nav-thread[data-id=" + thread_id + "]");
active = current_el.has(".forum-nav-thread-link.is-active").length !== 0; active = current_el.has(".forum-nav-thread-link.is-active").length !== 0;
current_el.replaceWith(content); current_el.replaceWith($content);
this.showMetadataAccordingToSort(); this.showMetadataAccordingToSort();
if (active) { if (active) {
return this.setActiveThread(thread_id); return this.setActiveThread(thread_id);
...@@ -246,12 +246,14 @@ ...@@ -246,12 +246,14 @@
}; };
DiscussionThreadListView.prototype.render = function() { DiscussionThreadListView.prototype.render = function() {
var self = this; var self = this,
$elem = this.template({
isCohorted: this.courseSettings.get("is_cohorted"),
isPrivilegedUser: DiscussionUtil.isPrivilegedUser()
});
this.timer = 0; this.timer = 0;
this.$el.html(this.template({ this.$el.empty();
isCohorted: this.courseSettings.get("is_cohorted"), this.$el.append($elem);
isPrivilegedUser: DiscussionUtil.isPrivilegedUser()
}));
this.$(".forum-nav-sort-control option").removeProp("selected"); this.$(".forum-nav-sort-control option").removeProp("selected");
this.$(".forum-nav-sort-control option[value=" + this.collection.sort_preference + "]") this.$(".forum-nav-sort-control option[value=" + this.collection.sort_preference + "]")
.prop("selected", true); .prop("selected", true);
...@@ -268,20 +270,17 @@ ...@@ -268,20 +270,17 @@
}; };
DiscussionThreadListView.prototype.renderThreads = function() { DiscussionThreadListView.prototype.renderThreads = function() {
var content, rendered, thread, _i, _len, _ref; var $content, thread, i, len;
this.$(".forum-nav-thread-list").html(""); this.$(".forum-nav-thread-list").empty();
rendered = $("<div></div>"); for (i = 0, len = this.displayedCollection.models.length; i < len; i++) {
_ref = this.displayedCollection.models; thread = this.displayedCollection.models[i];
for (_i = 0, _len = _ref.length; _i < _len; _i++) { $content = this.renderThread(thread);
thread = _ref[_i]; this.$(".forum-nav-thread-list").append($content);
content = this.renderThread(thread);
rendered.append(content);
} }
this.$(".forum-nav-thread-list").html(rendered.html());
this.showMetadataAccordingToSort(); this.showMetadataAccordingToSort();
this.renderMorePages(); this.renderMorePages();
this.updateSidebar(); this.updateSidebar();
return this.trigger("threads:rendered"); this.trigger("threads:rendered");
}; };
DiscussionThreadListView.prototype.showMetadataAccordingToSort = function() { DiscussionThreadListView.prototype.showMetadataAccordingToSort = function() {
...@@ -301,19 +300,15 @@ ...@@ -301,19 +300,15 @@
DiscussionThreadListView.prototype.renderMorePages = function() { DiscussionThreadListView.prototype.renderMorePages = function() {
if (this.displayedCollection.hasMorePages()) { if (this.displayedCollection.hasMorePages()) {
return this.$(".forum-nav-thread-list") edx.HtmlUtils.append(
.append( this.$(".forum-nav-thread-list"),
"<li class='forum-nav-load-more'>" + edx.HtmlUtils.template($("#nav-load-more-link").html())({})
" <a href='#' class='forum-nav-load-more-link'>" + gettext("Load more") + "</a>" + );
"</li>"
);
} }
}; };
DiscussionThreadListView.prototype.getLoadingContent = function(srText) { DiscussionThreadListView.prototype.getLoadingContent = function(srText) {
return '<div class="forum-nav-loading" tabindex="0">' + return edx.HtmlUtils.template($("#nav-loading-template").html())({srText: srText});
' <span class="icon fa fa-spinner fa-spin"/><span class="sr" role="alert">' + srText + '</span>' +
'</div>';
}; };
DiscussionThreadListView.prototype.loadMorePages = function(event) { DiscussionThreadListView.prototype.loadMorePages = function(event) {
...@@ -323,7 +318,8 @@ ...@@ -323,7 +318,8 @@
event.preventDefault(); event.preventDefault();
} }
loadMoreElem = this.$(".forum-nav-load-more"); loadMoreElem = this.$(".forum-nav-load-more");
loadMoreElem.html(this.getLoadingContent(gettext("Loading more threads"))); loadMoreElem.empty();
edx.HtmlUtils.append(loadMoreElem, this.getLoadingContent(gettext("Loading more threads")));
loadingElem = loadMoreElem.find(".forum-nav-loading"); loadingElem = loadMoreElem.find(".forum-nav-loading");
DiscussionUtil.makeFocusTrap(loadingElem); DiscussionUtil.makeFocusTrap(loadingElem);
loadingElem.focus(); loadingElem.focus();
...@@ -384,8 +380,8 @@ ...@@ -384,8 +380,8 @@
if (unreadCount > 0) { if (unreadCount > 0) {
content.find('.forum-nav-thread-comments-count').attr( content.find('.forum-nav-thread-comments-count').attr(
"data-tooltip", "data-tooltip",
interpolate( edx.StringUtils.interpolate(
ngettext('%(unread_count)s new comment', '%(unread_count)s new comments', unreadCount), ngettext('{unread_count} new comment', '{unread_count} new comments', unreadCount),
{unread_count: unreadCount}, {unread_count: unreadCount},
true true
) )
...@@ -407,18 +403,25 @@ ...@@ -407,18 +403,25 @@
}; };
DiscussionThreadListView.prototype.setActiveThread = function(thread_id) { DiscussionThreadListView.prototype.setActiveThread = function(thread_id) {
var $srElem;
this.$(".forum-nav-thread-link").find(".sr").remove(); this.$(".forum-nav-thread-link").find(".sr").remove();
this.$(".forum-nav-thread[data-id!='" + thread_id + "'] .forum-nav-thread-link") this.$(".forum-nav-thread[data-id!='" + thread_id + "'] .forum-nav-thread-link")
.removeClass("is-active"); .removeClass("is-active");
$srElem = edx.HtmlUtils.joinHtml(
edx.HtmlUtils.HTML('<span class="sr">'),
edx.HtmlUtils.ensureHtml(gettext("Current conversation")),
edx.HtmlUtils.HTML('</span>')
).toString();
this.$(".forum-nav-thread[data-id='" + thread_id + "'] .forum-nav-thread-link") this.$(".forum-nav-thread[data-id='" + thread_id + "'] .forum-nav-thread-link")
.addClass("is-active").find(".forum-nav-thread-wrapper-1") .addClass("is-active").find(".forum-nav-thread-wrapper-1")
.prepend('<span class="sr">' + gettext("Current conversation") + '</span>'); .prepend($srElem);
}; };
DiscussionThreadListView.prototype.goHome = function() { DiscussionThreadListView.prototype.goHome = function() {
var url; var url, $tpl_content;
this.template = _.template($("#discussion-home-template").html()); this.template = _.template($("#discussion-home-template").html());
$(".forum-content").html(this.template); $tpl_content = $(this.template());
$(".forum-content").empty().append($tpl_content);
$(".forum-nav-thread-list a").removeClass("is-active").find(".sr").remove(); $(".forum-nav-thread-list a").removeClass("is-active").find(".sr").remove();
$("input.email-setting").bind("click", this.updateEmailNotifications); $("input.email-setting").bind("click", this.updateEmailNotifications);
url = DiscussionUtil.urlFor("notifications_status", window.user.get("id")); url = DiscussionUtil.urlFor("notifications_status", window.user.get("id"));
...@@ -505,19 +508,19 @@ ...@@ -505,19 +508,19 @@
}; };
DiscussionThreadListView.prototype.getNameWidth = function(name) { DiscussionThreadListView.prototype.getNameWidth = function(name) {
var test, width; var $test, width;
test = $("<div>"); $test = $("<div>");
test.css({ $test.css({
"font-size": this.$(".forum-nav-browse-current").css('font-size'), "font-size": this.$(".forum-nav-browse-current").css('font-size'),
opacity: 0, opacity: 0,
position: 'absolute', position: 'absolute',
left: -1000, left: -1000,
top: -1000 top: -1000
}); });
$("body").append(test); $("body").append($test);
test.html(name); $test.text(name);
width = test.width(); width = $test.width();
test.remove(); $test.remove();
return width; return width;
}; };
...@@ -653,8 +656,7 @@ ...@@ -653,8 +656,7 @@
}; };
DiscussionThreadListView.prototype.searchFor = function(text) { DiscussionThreadListView.prototype.searchFor = function(text) {
var url, var url, self = this;
self = this;
this.clearSearchAlerts(); this.clearSearchAlerts();
this.clearFilters(); this.clearFilters();
this.mode = 'search'; this.mode = 'search';
...@@ -677,12 +679,16 @@ ...@@ -677,12 +679,16 @@
dataType: 'json', dataType: 'json',
$loading: $, $loading: $,
loadingCallback: function() { loadingCallback: function() {
return self.$(".forum-nav-thread-list") var element = self.$(".forum-nav-thread-list");
.html( element.empty();
"<li class='forum-nav-load-more'>" + edx.HtmlUtils.append(
self.getLoadingContent(gettext("Loading thread list")) + element,
"</li>" edx.HtmlUtils.joinHtml(
); edx.HtmlUtils.HTML("<li class='forum-nav-load-more'>"),
self.getLoadingContent(gettext("Loading thread list")),
edx.HtmlUtils.HTML("</li>")
)
);
}, },
loadedCallback: function() { loadedCallback: function() {
return self.$(".forum-nav-thread-list .forum-nav-load-more").remove(); return self.$(".forum-nav-thread-list .forum-nav-load-more").remove();
...@@ -697,17 +703,22 @@ ...@@ -697,17 +703,22 @@
if (!_.isNull(response.corrected_text)) { if (!_.isNull(response.corrected_text)) {
noResponseMsg = _.escape( noResponseMsg = _.escape(
gettext( gettext(
'No results found for %(original_query)s. ' + 'No results found for {original_query}. ' +
'Showing results for %(suggested_query)s.' 'Showing results for {suggested_query}.'
) )
); );
message = interpolate( message = edx.HtmlUtils.interpolateHtml(
noResponseMsg, noResponseMsg,
{ {
"original_query": "<em>" + _.escape(text) + "</em>", "original_query": edx.HtmlUtils.joinHtml(
"suggested_query": "<em>" + response.corrected_text + "</em>" edx.HtmlUtils.HTML("<em>"), text, edx.HtmlUtils.HTML("</em>")
}, ),
true "suggested_query": edx.HtmlUtils.joinHtml(
edx.HtmlUtils.HTML("<em>"),
response.corrected_text ,
edx.HtmlUtils.HTML("</em>")
)
}
); );
self.addSearchAlert(message); self.addSearchAlert(message);
} else if (response.discussion_data.length === 0) { } else if (response.discussion_data.length === 0) {
...@@ -733,14 +744,20 @@ ...@@ -733,14 +744,20 @@
dataType: 'json', dataType: 'json',
error: function() {}, error: function() {},
success: function(response) { success: function(response) {
var message; var message, username;
if (response.users.length > 0) { if (response.users.length > 0) {
message = interpolate(_.escape(gettext('Show posts by %(username)s.')), { username = edx.HtmlUtils.joinHtml(
"username": _.template('<a class="link-jump" href="<%= url %>"><%- username %></a>')({ edx.HtmlUtils.interpolateHtml(
url: DiscussionUtil.urlFor("user_profile", response.users[0].id), edx.HtmlUtils.HTML('<a class="link-jump" href="{url}">'),
username: response.users[0].username {url: DiscussionUtil.urlFor("user_profile", response.users[0].id)}
}) ),
}, true); response.users[0].username,
edx.HtmlUtils.HTML("</a>")
);
message = edx.HtmlUtils.interpolateHtml(
gettext('Show posts by {username}.'), {"username": username}
);
return self.addSearchAlert(message, 'search-by-user'); return self.addSearchAlert(message, 'search-by-user');
} }
} }
...@@ -764,15 +781,15 @@ ...@@ -764,15 +781,15 @@
}; };
DiscussionThreadListView.prototype.updateEmailNotifications = function() { DiscussionThreadListView.prototype.updateEmailNotifications = function() {
var checkbox, checked, urlName; var $checkbox, checked, urlName;
checkbox = $('input.email-setting'); $checkbox = $('input.email-setting');
checked = checkbox.prop('checked'); checked = $checkbox.prop('checked');
urlName = (checked) ? "enable_notifications" : "disable_notifications"; urlName = (checked) ? "enable_notifications" : "disable_notifications";
DiscussionUtil.safeAjax({ DiscussionUtil.safeAjax({
url: DiscussionUtil.urlFor(urlName), url: DiscussionUtil.urlFor(urlName),
type: "POST", type: "POST",
error: function() { error: function() {
checkbox.prop('checked', !checked); $checkbox.prop('checked', !checked);
} }
}); });
}; };
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
} }
DiscussionThreadProfileView.prototype.render = function() { DiscussionThreadProfileView.prototype.render = function() {
var element, params; var params;
this.convertMath(); this.convertMath();
this.abbreviateBody(); this.abbreviateBody();
params = $.extend(this.model.toJSON(), { params = $.extend(this.model.toJSON(), {
...@@ -42,26 +42,24 @@ ...@@ -42,26 +42,24 @@
} }
}); });
} }
this.$el.html(_.template($("#profile-thread-template").html())(params)); edx.HtmlUtils.setHtml(
this.$el,
edx.HtmlUtils.template($("#profile-thread-template").html())(params)
);
this.$("span.timeago").timeago(); this.$("span.timeago").timeago();
element = this.$(".post-body"); DiscussionUtil.typesetMathJax(this.$(".post-body"));
if (typeof MathJax !== "undefined" && MathJax !== null) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
return this; return this;
}; };
DiscussionThreadProfileView.prototype.convertMath = function() { DiscussionThreadProfileView.prototype.convertMath = function() {
return this.model.set( var htmlSnippet = DiscussionUtil.markdownWithHighlight(this.model.get('body'));
'markdownBody', this.model.set('markdownBody', htmlSnippet);
DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(this.model.get('body')))
);
}; };
DiscussionThreadProfileView.prototype.abbreviateBody = function() { DiscussionThreadProfileView.prototype.abbreviateBody = function() {
var abbreviated; var abbreviated;
abbreviated = DiscussionUtil.abbreviateHTML(this.model.get('markdownBody'), 140); abbreviated = DiscussionUtil.abbreviateHTML(this.model.get('markdownBody'), 140);
return this.model.set('abbreviatedBody', abbreviated); this.model.set('abbreviatedBody', abbreviated);
}; };
return DiscussionThreadProfileView; return DiscussionThreadProfileView;
......
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
DiscussionThreadShowView.prototype.renderTemplate = function() { DiscussionThreadShowView.prototype.renderTemplate = function() {
var context; var context;
this.template = _.template($("#thread-show-template").html());
context = $.extend({ context = $.extend({
mode: this.mode, mode: this.mode,
flagged: this.model.isFlagged(), flagged: this.model.isFlagged(),
...@@ -46,27 +45,25 @@ ...@@ -46,27 +45,25 @@
cid: this.model.cid, cid: this.model.cid,
readOnly: $('.discussion-module').data('read-only') readOnly: $('.discussion-module').data('read-only')
}, this.model.attributes); }, this.model.attributes);
return this.template(context); return edx.HtmlUtils.template($("#thread-show-template").html())(context);
}; };
DiscussionThreadShowView.prototype.render = function() { DiscussionThreadShowView.prototype.render = function() {
this.$el.html(this.renderTemplate()); edx.HtmlUtils.setHtml(
this.$el,
this.renderTemplate()
);
this.delegateEvents(); this.delegateEvents();
this.renderAttrs(); this.renderAttrs();
this.$("span.timeago").timeago(); this.$("span.timeago").timeago();
this.convertMath(); this.convertMath();
this.highlight(this.$(".post-body")); this.$(".post-body");
this.highlight(this.$("h1,h3")); this.$("h1,h3");
return this; return this;
}; };
DiscussionThreadShowView.prototype.convertMath = function() { DiscussionThreadShowView.prototype.convertMath = function() {
var element; DiscussionUtil.convertMath(this.$(".post-body"));
element = this.$(".post-body");
element.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(element.text())));
if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
}; };
DiscussionThreadShowView.prototype.edit = function(event) { DiscussionThreadShowView.prototype.edit = function(event) {
...@@ -77,12 +74,6 @@ ...@@ -77,12 +74,6 @@
return this.trigger("thread:_delete", event); return this.trigger("thread:_delete", event);
}; };
DiscussionThreadShowView.prototype.highlight = function(el) {
if (el.html()) {
return el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>"));
}
};
return DiscussionThreadShowView; return DiscussionThreadShowView;
})(DiscussionContentShowView); })(DiscussionContentShowView);
......
...@@ -134,7 +134,9 @@ ...@@ -134,7 +134,9 @@
DiscussionThreadView.prototype.render = function() { DiscussionThreadView.prototype.render = function() {
var self = this; var self = this;
this.$el.html(this.renderTemplate()); var $element = $(this.renderTemplate());
this.$el.empty();
this.$el.append($element);
this.delegateEvents(); this.delegateEvents();
this.renderShowView(); this.renderShowView();
this.renderAttrs(); this.renderAttrs();
...@@ -216,7 +218,7 @@ ...@@ -216,7 +218,7 @@
} }
}; };
DiscussionThreadView.prototype.loadResponses = function(responseLimit, elem, firstLoad) { DiscussionThreadView.prototype.loadResponses = function(responseLimit, $elem, firstLoad) {
var takeFocus, var takeFocus,
self = this; self = this;
takeFocus = this.mode === "tab" ? false : true; takeFocus = this.mode === "tab" ? false : true;
...@@ -228,8 +230,8 @@ ...@@ -228,8 +230,8 @@
resp_skip: this.responses.size(), resp_skip: this.responses.size(),
resp_limit: responseLimit ? responseLimit : void 0 resp_limit: responseLimit ? responseLimit : void 0
}, },
$elem: elem, $elem: $elem,
$loading: elem, $loading: $elem,
takeFocus: takeFocus, takeFocus: takeFocus,
complete: function() { complete: function() {
self.responsesRequest = null; self.responsesRequest = null;
...@@ -280,20 +282,20 @@ ...@@ -280,20 +282,20 @@
}; };
DiscussionThreadView.prototype.renderResponseCountAndPagination = function(responseTotal) { DiscussionThreadView.prototype.renderResponseCountAndPagination = function(responseTotal) {
var buttonText, loadMoreButton, responseCountFormat, responseLimit, responsePagination, var buttonText, $loadMoreButton, responseCountFormat, responseLimit, responsePagination,
responsesRemaining, showingResponsesText, self = this; responsesRemaining, showingResponsesText, self = this;
if (this.isQuestion() && this.markedAnswers.length !== 0) { if (this.isQuestion() && this.markedAnswers.length !== 0) {
responseCountFormat = ngettext( responseCountFormat = ngettext(
"%(numResponses)s other response", "%(numResponses)s other responses", responseTotal "{numResponses} other response", "{numResponses} other responses", responseTotal
); );
} else { } else {
responseCountFormat = ngettext( responseCountFormat = ngettext(
"%(numResponses)s response", "%(numResponses)s responses", responseTotal "{numResponses} response", "{numResponses} responses", responseTotal
); );
} }
this.$el.find(".response-count").html(interpolate(responseCountFormat, { this.$el.find(".response-count").text(
numResponses: responseTotal edx.StringUtils.interpolate(responseCountFormat, {numResponses: responseTotal}, true)
}, true)); );
responsePagination = this.$el.find(".response-pagination"); responsePagination = this.$el.find(".response-pagination");
responsePagination.empty(); responsePagination.empty();
if (responseTotal > 0) { if (responseTotal > 0) {
...@@ -302,9 +304,9 @@ ...@@ -302,9 +304,9 @@
showingResponsesText = gettext("Showing all responses"); showingResponsesText = gettext("Showing all responses");
} }
else { else {
showingResponsesText = interpolate( showingResponsesText = edx.StringUtils.interpolate(
ngettext( ngettext(
"Showing first response", "Showing first %(numResponses)s responses", "Showing first response", "Showing first {numResponses} responses",
this.responses.size() this.responses.size()
), ),
{ numResponses: this.responses.size() }, { numResponses: this.responses.size() },
...@@ -313,22 +315,22 @@ ...@@ -313,22 +315,22 @@
} }
responsePagination.append($("<span>") responsePagination.append($("<span>")
.addClass("response-display-count").html(_.escape(showingResponsesText))); .addClass("response-display-count").text(showingResponsesText));
if (responsesRemaining > 0) { if (responsesRemaining > 0) {
if (responsesRemaining < SUBSEQUENT_RESPONSE_PAGE_SIZE) { if (responsesRemaining < SUBSEQUENT_RESPONSE_PAGE_SIZE) {
responseLimit = null; responseLimit = null;
buttonText = gettext("Load all responses"); buttonText = gettext("Load all responses");
} else { } else {
responseLimit = SUBSEQUENT_RESPONSE_PAGE_SIZE; responseLimit = SUBSEQUENT_RESPONSE_PAGE_SIZE;
buttonText = interpolate(gettext("Load next %(numResponses)s responses"), { buttonText = edx.StringUtils.interpolate(gettext("Load next {numResponses} responses"), {
numResponses: responseLimit numResponses: responseLimit
}, true); }, true);
} }
loadMoreButton = $("<button>").addClass("load-response-button").html(_.escape(buttonText)); $loadMoreButton = $("<button>").addClass("load-response-button").text(buttonText);
loadMoreButton.click(function() { $loadMoreButton.click(function() {
return self.loadResponses(responseLimit, loadMoreButton); return self.loadResponses(responseLimit, $loadMoreButton);
}); });
return responsePagination.append(loadMoreButton); return responsePagination.append($loadMoreButton);
} }
} }
}; };
......
...@@ -37,12 +37,14 @@ ...@@ -37,12 +37,14 @@
ResponseCommentShowView.prototype.tagName = "li"; ResponseCommentShowView.prototype.tagName = "li";
ResponseCommentShowView.prototype.render = function() { ResponseCommentShowView.prototype.render = function() {
this.template = _.template($("#response-comment-show-template").html()); var template = edx.HtmlUtils.template($("#response-comment-show-template").html());
this.$el.html(this.template(_.extend({ var context = _.extend({
cid: this.model.cid, cid: this.model.cid,
author_display: this.getAuthorDisplay(), author_display: this.getAuthorDisplay(),
readOnly: $('.discussion-module').data('read-only') readOnly: $('.discussion-module').data('read-only')
}, this.model.attributes))); }, this.model.attributes);
edx.HtmlUtils.setHtml(this.$el, template(context));
this.delegateEvents(); this.delegateEvents();
this.renderAttrs(); this.renderAttrs();
this.$el.find(".timeago").timeago(); this.$el.find(".timeago").timeago();
...@@ -52,22 +54,25 @@ ...@@ -52,22 +54,25 @@
}; };
ResponseCommentShowView.prototype.addReplyLink = function() { ResponseCommentShowView.prototype.addReplyLink = function() {
var html, name, p, _ref; var html, name;
if (this.model.hasOwnProperty('parent')) { if (this.model.hasOwnProperty('parent')) {
name = (_ref = this.model.parent.get('username')) !== null ? _ref : gettext("anonymous"); name = this.model.parent.get('username') || gettext("anonymous");
html = "<a href='#comment_" + this.model.parent.id + "'>@" + name + "</a>: "; html = edx.HtmlUtils.interpolateHtml(
p = this.$('.response-body p:first'); edx.HtmlUtils.HTML("<a href='#comment_{parent_id}'>@{name}</a>: "),
return p.prepend(html); {
parent_id: this.model.parent.id,
name: name
}
);
return edx.HtmlUtils.prepend(
this.$('.response-body p:first'),
html
);
} }
}; };
ResponseCommentShowView.prototype.convertMath = function() { ResponseCommentShowView.prototype.convertMath = function() {
var body; DiscussionUtil.convertMath(this.$el.find(".response-body"));
body = this.$el.find(".response-body");
body.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(body.text())));
if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, body[0]]);
}
}; };
ResponseCommentShowView.prototype._delete = function(event) { ResponseCommentShowView.prototype._delete = function(event) {
......
...@@ -33,19 +33,18 @@ ...@@ -33,19 +33,18 @@
}; };
ThreadResponseShowView.prototype.renderTemplate = function() { ThreadResponseShowView.prototype.renderTemplate = function() {
var context; var template = edx.HtmlUtils.template($("#thread-response-show-template").html()),
this.template = _.template($("#thread-response-show-template").html()); context = _.extend({
context = _.extend({ cid: this.model.cid,
cid: this.model.cid, author_display: this.getAuthorDisplay(),
author_display: this.getAuthorDisplay(), endorser_display: this.getEndorserDisplay(),
endorser_display: this.getEndorserDisplay(), readOnly: $('.discussion-module').data('read-only')
readOnly: $('.discussion-module').data('read-only') }, this.model.attributes);
}, this.model.attributes); return template(context);
return this.template(context);
}; };
ThreadResponseShowView.prototype.render = function() { ThreadResponseShowView.prototype.render = function() {
this.$el.html(this.renderTemplate()); edx.HtmlUtils.setHtml(this.$el, this.renderTemplate());
this.delegateEvents(); this.delegateEvents();
this.renderAttrs(); this.renderAttrs();
this.$el.find(".posted-details .timeago").timeago(); this.$el.find(".posted-details .timeago").timeago();
...@@ -54,12 +53,7 @@ ...@@ -54,12 +53,7 @@
}; };
ThreadResponseShowView.prototype.convertMath = function() { ThreadResponseShowView.prototype.convertMath = function() {
var element; DiscussionUtil.convertMath(this.$(".response-body"));
element = this.$(".response-body");
element.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(element.text())));
if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
}; };
ThreadResponseShowView.prototype.edit = function(event) { ThreadResponseShowView.prototype.edit = function(event) {
......
...@@ -269,7 +269,7 @@ ...@@ -269,7 +269,7 @@
}).get()).toEqual(expectedMessages); }).get()).toEqual(expectedMessages);
}; };
getAlertMessagesAndClasses = function () { getAlertMessagesAndClasses = function() {
return $(".search-alert").map(function() { return $(".search-alert").map(function() {
return { return {
text: $('.message', this).html(), text: $('.message', this).html(),
......
...@@ -72,12 +72,12 @@ ...@@ -72,12 +72,12 @@
'thread-response-edit', 'response-comment-show', 'response-comment-edit', 'thread-list-item', 'thread-response-edit', 'response-comment-show', 'response-comment-edit', 'thread-list-item',
'discussion-home', 'search-alert', 'new-post', 'thread-type', 'new-post-menu-entry', 'discussion-home', 'search-alert', 'new-post', 'thread-type', 'new-post-menu-entry',
'new-post-menu-category', 'topic', 'post-user-display', 'inline-discussion', 'pagination', 'new-post-menu-category', 'topic', 'post-user-display', 'inline-discussion', 'pagination',
'user-profile', 'profile-thread' 'user-profile', 'profile-thread', 'customwmd-prompt', 'nav-loading'
]; ];
templateNamesNoTrailingTemplate = [ templateNamesNoTrailingTemplate = [
'forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-vote', 'forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-vote',
'forum-action-report', 'forum-action-pin', 'forum-action-close', 'forum-action-edit', 'forum-action-report', 'forum-action-pin', 'forum-action-close', 'forum-action-edit',
'forum-action-delete', 'forum-actions' 'forum-action-delete', 'forum-actions', 'alert-popup', 'nav-load-more-link'
]; ];
for (_i = 0, _len = templateNames.length; _i < _len; _i++) { for (_i = 0, _len = templateNames.length; _i < _len; _i++) {
templateName = templateNames[_i]; templateName = templateNames[_i];
......
<div class='modal' role='alertdialog' id='discussion-alert' aria-describedby='discussion-alert-message'>
<div class='inner-wrapper discussion-alert-wrapper'>
<button class='close-modal dismiss' title='<%- gettext("Close") %>''>
<span class='icon fa fa-times' aria-hidden='true'></span>
</button>
<header>
<h2/></h2>
<hr/>
</header>
<p id='discussion-alert-message'></p>
<hr/>
<button class='dismiss'><%- gettext("OK") %></button>
</div>
</div>
<li class='forum-nav-load-more'>
<a href='#' class='forum-nav-load-more-link'>
<%- gettext("Load more") %>
</a>
</li>
<div class="forum-nav-loading" tabindex="0">
<span class="icon fa fa-spinner fa-spin"/>
<span class="sr" role="alert"><%- srText %></span>
</div>
<div class="search-alert <%= css_class %>" id="search-alert-<%- cid %>"> <div class="search-alert <%- css_class %>" id="search-alert-<%- cid %>">
<div class="search-alert-content"> <div class="search-alert-content">
<p class="message"><%= message %></p> <p class="message"><%= HtmlUtils.ensureHtml(messageHtml) %></p>
</div> </div>
<div class="search-alert-controls"> <div class="search-alert-controls">
......
...@@ -35,6 +35,8 @@ var options = { ...@@ -35,6 +35,8 @@ var options = {
{pattern: 'common/js/vendor/backbone.js', included: true}, {pattern: 'common/js/vendor/backbone.js', included: true},
{pattern: 'edx-ui-toolkit/js/utils/global-loader.js', included: true}, {pattern: 'edx-ui-toolkit/js/utils/global-loader.js', included: true},
{pattern: 'edx-ui-toolkit/js/utils/string-utils.js', included: true},
{pattern: 'edx-ui-toolkit/js/utils/html-utils.js', included: true},
{pattern: 'js/vendor/jasmine-imagediff.js', included: true}, {pattern: 'js/vendor/jasmine-imagediff.js', included: true},
{pattern: 'common/js/spec_helpers/jasmine-extensions.js', included: true}, {pattern: 'common/js/spec_helpers/jasmine-extensions.js', included: true},
......
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%! import json %> <%! from openedx.core.djangolib.js_utils import js_escaped_string %>
<script type="text/javascript"> <script type="text/javascript">
window.PLATFORM_NAME = ${json.dumps(settings.PLATFORM_NAME)}; window.PLATFORM_NAME = "${settings.PLATFORM_NAME | n, js_escaped_string}";
window.ENABLE_DISCUSSION_HOME_PANEL = ${json.dumps(settings.FEATURES.get('ENABLE_DISCUSSION_HOME_PANEL', False))}; % if settings.FEATURES.get('ENABLE_DISCUSSION_HOME_PANEL', False):
window.ENABLE_DISCUSSION_HOME_PANEL = true;
% else:
window.ENABLE_DISCUSSION_HOME_PANEL = false;
% endif
</script> </script>
<% <%
...@@ -11,7 +16,14 @@ template_names = [ ...@@ -11,7 +16,14 @@ template_names = [
'thread', 'thread-show', 'thread-edit', 'thread-response', 'thread-response-show', 'thread-response-edit', 'thread', 'thread-show', 'thread-edit', 'thread-response', 'thread-response-show', 'thread-response-edit',
'response-comment-show', 'response-comment-edit', 'thread-list-item', 'discussion-home', 'search-alert', 'response-comment-show', 'response-comment-edit', 'thread-list-item', 'discussion-home', 'search-alert',
'new-post', 'thread-type', 'new-post-menu-entry', 'new-post-menu-category', 'topic', 'post-user-display', 'new-post', 'thread-type', 'new-post-menu-entry', 'new-post-menu-category', 'topic', 'post-user-display',
'inline-discussion', 'pagination', 'user-profile', 'profile-thread', 'customwmd-prompt' 'inline-discussion', 'pagination', 'user-profile', 'profile-thread', 'customwmd-prompt', 'nav-loading'
]
## same, but without trailing "-template" in script ID - these templates does not contain any free variables
template_names_no_suffix = [
'forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-vote', 'forum-action-report',
'forum-action-pin', 'forum-action-close', 'forum-action-edit', 'forum-action-delete', 'forum-actions',
'alert-popup', 'nav-load-more-link'
] ]
%> %>
...@@ -21,8 +33,8 @@ template_names = [ ...@@ -21,8 +33,8 @@ template_names = [
</script> </script>
% endfor % endfor
## same, but without trailing "-template" in script ID
% for template_name in ['forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-vote', 'forum-action-report', 'forum-action-pin', 'forum-action-close', 'forum-action-edit', 'forum-action-delete', 'forum-actions']: % for template_name in template_names_no_suffix:
<script aria-hidden="true" type="text/template" id="${template_name}"> <script aria-hidden="true" type="text/template" id="${template_name}">
<%static:include path="common/templates/discussion/${template_name}.underscore" /> <%static:include path="common/templates/discussion/${template_name}.underscore" />
</script> </script>
......
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%! <%!
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
...@@ -6,7 +7,7 @@ from django.template.defaultfilters import escapejs ...@@ -6,7 +7,7 @@ from django.template.defaultfilters import escapejs
%> %>
<%block name="bodyclass">discussion</%block> <%block name="bodyclass">discussion</%block>
<%block name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default) | h}</%block> <%block name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}</%block>
<%block name="headextra"> <%block name="headextra">
<%static:css group='style-course-vendor'/> <%static:css group='style-course-vendor'/>
...@@ -33,7 +34,7 @@ from django.template.defaultfilters import escapejs ...@@ -33,7 +34,7 @@ from django.template.defaultfilters import escapejs
</nav> </nav>
</section> </section>
<section class="course-content container discussion-user-threads" data-course-id="${course.id | h}" data-course-name="${course.display_name_with_default_escaped | h}" data-threads="${threads | h}" data-user-info="${user_info | h}" data-page="${page | h}" data-num-pages="${num_pages | h}"/> <section class="course-content container discussion-user-threads" data-course-id="${course.id}" data-course-name="${course.display_name_with_default}" data-threads="${threads}" data-user-info="${user_info}" data-page="${page}" data-num-pages="${num_pages}"/>
</div> </div>
</section> </section>
......
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