Commit 76b8e2e8 by Andy Armstrong

Merge pull request #11631 from edx/fedx/upgrade-libraries

Upgrade Underscore.js and Underscore.string.js
parents f3d000ec e5c7fdda
...@@ -129,9 +129,9 @@ function ($, _, Backbone, gettext, ...@@ -129,9 +129,9 @@ function ($, _, Backbone, gettext,
if (event && event.preventDefault) { event.preventDefault(); } if (event && event.preventDefault) { event.preventDefault(); }
var model = this.model; var model = this.model;
var self = this; var self = this;
var titleText = gettext('Delete "<%= signatoryName %>" from the list of signatories?'); var titleTextTemplate = _.template(gettext('Delete "<%= signatoryName %>" from the list of signatories?'));
var confirm = new PromptView.Warning({ var confirm = new PromptView.Warning({
title: _.template(titleText, {signatoryName: model.get('name')}), title: titleTextTemplate({signatoryName: model.get('name')}),
message: gettext('This action cannot be undone.'), message: gettext('This action cannot be undone.'),
actions: { actions: {
primary: { primary: {
......
...@@ -66,9 +66,10 @@ var CourseGrader = Backbone.Model.extend({ ...@@ -66,9 +66,10 @@ var CourseGrader = Backbone.Model.extend({
else attrs.drop_count = intDropCount; else attrs.drop_count = intDropCount;
} }
if (_.has(attrs, 'min_count') && _.has(attrs, 'drop_count') && !_.has(errors, 'min_count') && !_.has(errors, 'drop_count') && attrs.drop_count > attrs.min_count) { if (_.has(attrs, 'min_count') && _.has(attrs, 'drop_count') && !_.has(errors, 'min_count') && !_.has(errors, 'drop_count') && attrs.drop_count > attrs.min_count) {
errors.drop_count = _.template( var template = _.template(
gettext("Cannot drop more <% attrs.types %> than will assigned."), gettext("Cannot drop more <%= types %> assignments than are assigned.")
attrs, {variable: 'attrs'}); );
errors.drop_count = template({types: attrs.type});
} }
if (!_.isEmpty(errors)) return errors; if (!_.isEmpty(errors)) return errors;
} }
......
...@@ -15,8 +15,7 @@ var FileUpload = Backbone.Model.extend({ ...@@ -15,8 +15,7 @@ var FileUpload = Backbone.Model.extend({
validate: function(attrs, options) { validate: function(attrs, options) {
if(attrs.selectedFile && !this.checkTypeValidity(attrs.selectedFile)) { if(attrs.selectedFile && !this.checkTypeValidity(attrs.selectedFile)) {
return { return {
message: _.template( message: _.template(gettext("Only <%= fileTypes %> files can be uploaded. Please select a file ending in <%= fileExtensions %> to upload."))( // jshint ignore:line
gettext("Only <%= fileTypes %> files can be uploaded. Please select a file ending in <%= fileExtensions %> to upload."),
this.formatValidTypes() this.formatValidTypes()
), ),
attributes: {selectedFile: true} attributes: {selectedFile: true}
...@@ -64,7 +63,7 @@ var FileUpload = Backbone.Model.extend({ ...@@ -64,7 +63,7 @@ var FileUpload = Backbone.Model.extend({
} }
var or = gettext('or'); var or = gettext('or');
var formatTypes = function(types) { var formatTypes = function(types) {
return _.template('<%= initial %> <%= or %> <%= last %>', { return _.template('<%= initial %> <%= or %> <%= last %>')({
initial: _.initial(types).join(', '), initial: _.initial(types).join(', '),
or: or, or: or,
last: _.last(types) last: _.last(types)
......
...@@ -359,12 +359,12 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "draggabilly", ...@@ -359,12 +359,12 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "draggabilly",
makeDraggable: function (element, options) { makeDraggable: function (element, options) {
var draggable; var draggable;
options = _.defaults({ options = _.defaults({
type: null, type: undefined,
handleClass: null, handleClass: undefined,
droppableClass: null, droppableClass: undefined,
parentLocationSelector: null, parentLocationSelector: undefined,
refresh: null, refresh: undefined,
ensureChildrenRendered: null ensureChildrenRendered: undefined
}, options); }, options);
if ($(element).data('droppable-class') !== options.droppableClass) { if ($(element).data('droppable-class') !== options.droppableClass) {
......
...@@ -67,7 +67,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset ...@@ -67,7 +67,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
ViewUtils.hideLoadingIndicator(); ViewUtils.hideLoadingIndicator();
// Create the table // Create the table
this.$el.html(_.template(asset_library_template, {typeData: this.typeData})); this.$el.html(_.template(asset_library_template)({typeData: this.typeData}));
tableBody = this.$('#asset-table-body'); tableBody = this.$('#asset-table-body');
this.tableBody = tableBody; this.tableBody = tableBody;
this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')}); this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')});
......
/*global course */
define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gettext", "js/models/uploads", "js/views/uploads"], define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gettext", "js/models/uploads", "js/views/uploads"],
function(BaseView, _, str, $, gettext, FileUploadModel, UploadDialogView) { function(BaseView, _, str, $, gettext, FileUploadModel, UploadDialogView) {
_.str = str; // used in template _.str = str; // used in template
...@@ -52,10 +54,8 @@ define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gette ...@@ -52,10 +54,8 @@ define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gette
asset_path: this.$("input.chapter-asset-path").val() asset_path: this.$("input.chapter-asset-path").val()
}); });
var msg = new FileUploadModel({ var msg = new FileUploadModel({
title: _.template( title: _.template(gettext("Upload a new PDF to “<%= name %>”"))(
gettext("Upload a new PDF to “<%= name %>”"), {name: course.escape('name')}),
{name: window.course.escape('name')}
),
message: gettext("Please select a PDF file to upload."), message: gettext("Please select a PDF file to upload."),
mimeTypes: ['application/pdf'] mimeTypes: ['application/pdf']
}); });
......
...@@ -54,8 +54,8 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview", ...@@ -54,8 +54,8 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview",
title: messages.alreadyMember.title, title: messages.alreadyMember.title,
message: _.template( message: _.template(
messages.alreadyMember.messageTpl, messages.alreadyMember.messageTpl,
{email: email, container: containerName}, {interpolate: /\{(.+?)}/g})(
{interpolate: /\{(.+?)}/g} {email: email, container: containerName}
), ),
actions: { actions: {
primary: { primary: {
...@@ -140,7 +140,9 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview", ...@@ -140,7 +140,9 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview",
roles = _.object(_.pluck(this.roles, 'key'), _.pluck(this.roles, "name")), roles = _.object(_.pluck(this.roles, 'key'), _.pluck(this.roles, "name")),
adminRoleCount = this.getAdminRoleCount(), adminRoleCount = this.getAdminRoleCount(),
viewHelpers = { viewHelpers = {
format: function (template, data) { return _.template(template, data, {interpolate: /\{(.+?)}/g}); } format: function (template, data) {
return _.template(template, {interpolate: /\{(.+?)}/g})(data);
}
}; };
for (var i = 0; i < this.users.length; i++) { for (var i = 0; i < this.users.length; i++) {
var user = this.users[i], var user = this.users[i],
...@@ -284,8 +286,8 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview", ...@@ -284,8 +286,8 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview",
title: self.messages.deleteUser.title, title: self.messages.deleteUser.title,
message: _.template( message: _.template(
self.messages.deleteUser.messageTpl, self.messages.deleteUser.messageTpl,
{email: email, container: self.containerName}, {interpolate: /\{(.+?)}/g})(
{interpolate: /\{(.+?)}/g} {email: email, container: self.containerName}
), ),
actions: { actions: {
primary: { primary: {
......
define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "common/js/components/views/feedback_notification",
"js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module", "common/js/components/utils/view_utils"],
function (domReady, $, ui, _, gettext, NotificationView, CancelOnEscape,
DateUtils, ModuleUtils, ViewUtils) {
var modalSelector = '.edit-section-publish-settings';
var toggleSections = function(e) {
e.preventDefault();
var $section = $('.courseware-section');
var $button = $(this);
var $labelCollapsed = $('<i class="icon fa fa-arrow-up"></i> <span class="label">' +
gettext('Collapse All Sections') + '</span>');
var $labelExpanded = $('<i class="icon fa fa-arrow-down"></i> <span class="label">' +
gettext('Expand All Sections') + '</span>');
var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
$button.toggleClass('is-activated').html(buttonLabel);
if ($button.hasClass('is-activated')) {
$section.addClass('collapsed');
// first child in order to avoid the icons on the subsection lists which are not in the first child
$section.find('header .expand-collapse').removeClass('collapse').addClass('expand');
} else {
$section.removeClass('collapsed');
// first child in order to avoid the icons on the subsection lists which are not in the first child
$section.find('header .expand-collapse').removeClass('expand').addClass('collapse');
}
};
var toggleSubmodules = function(e) {
e.preventDefault();
$(this).toggleClass('expand collapse');
$(this).closest('.is-collapsible, .window').toggleClass('collapsed');
};
var closeModalNew = function (e) {
if (e) {
e.preventDefault();
}
$('body').removeClass('modal-window-is-shown');
$('.edit-section-publish-settings').removeClass('is-shown');
};
var editSectionPublishDate = function (e) {
e.preventDefault();
var $modal = $(modalSelector);
$modal.attr('data-locator', $(this).attr('data-locator'));
$modal.find('.start-date').val($(this).attr('data-date'));
$modal.find('.start-time').val($(this).attr('data-time'));
if ($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
$modal.find('.save-button').hide();
}
$modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
$('body').addClass('modal-window-is-shown');
$('.edit-section-publish-settings').addClass('is-shown');
};
var saveSetSectionScheduleDate = function (e) {
e.preventDefault();
var datetime = DateUtils.getDate(
$('.edit-section-publish-settings .start-date'),
$('.edit-section-publish-settings .start-time')
);
var locator = $(modalSelector).attr('data-locator');
analytics.track('Edited Section Release Date', {
'course': course_location_analytics,
'id': locator,
'start': datetime
});
var saving = new NotificationView.Mini({
title: gettext("Saving")
});
saving.show();
// call into server to commit the new order
$.ajax({
url: ModuleUtils.getUpdateUrl(locator),
type: "PUT",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
'metadata': {
'start': datetime
}
})
}).success(function() {
var pad2 = function(number) {
// pad a number to two places: useful for formatting months, days, hours, etc
// when displaying a date/time
return (number < 10 ? '0' : '') + number;
};
var $thisSection = $('.courseware-section[data-locator="' + locator + '"]');
var html = _.template(
'<span class="published-status">' +
'<strong>' + gettext("Release date:") + '&nbsp;</strong>' +
gettext("{month}/{day}/{year} at {hour}:{minute} UTC") +
'</span>' +
'<a href="#" class="edit-release-date action" data-date="{month}/{day}/{year}" data-time="{hour}:{minute}" data-locator="{locator}"><i class="icon fa fa-time"></i> <span class="sr">' +
gettext("Edit section release date") +
'</span></a>',
{year: datetime.getUTCFullYear(), month: pad2(datetime.getUTCMonth() + 1), day: pad2(datetime.getUTCDate()),
hour: pad2(datetime.getUTCHours()), minute: pad2(datetime.getUTCMinutes()),
locator: locator},
{interpolate: /\{(.+?)\}/g});
$thisSection.find('.section-published-date').html(html);
saving.hide();
closeModalNew();
});
};
var addNewSection = function (e) {
e.preventDefault();
$(e.target).addClass('disabled');
var $newSection = $($('#new-section-template').html());
var $cancelButton = $newSection.find('.new-section-name-cancel');
$('.courseware-overview').prepend($newSection);
$newSection.find('.new-section-name').focus().select();
$newSection.find('.section-name-form').bind('submit', saveNewSection);
$cancelButton.bind('click', cancelNewSection);
CancelOnEscape($cancelButton);
};
var saveNewSection = function (e) {
e.preventDefault();
var $saveButton = $(this).find('.new-section-name-save');
var parent = $saveButton.data('parent');
var category = $saveButton.data('category');
var display_name = $(this).find('.new-section-name').val();
analytics.track('Created a Section', {
'course': course_location_analytics,
'display_name': display_name
});
$.postJSON(ModuleUtils.getUpdateUrl(), {
'parent_locator': parent,
'category': category,
'display_name': display_name
},
function(data) {
if (data.locator != undefined) location.reload();
});
};
var cancelNewSection = function (e) {
e.preventDefault();
$('.new-courseware-section-button').removeClass('disabled');
$(this).parents('section.new-section').remove();
};
var addNewSubsection = function (e) {
e.preventDefault();
var $section = $(this).closest('.courseware-section');
var $newSubsection = $($('#new-subsection-template').html());
$section.find('.subsection-list > ol').append($newSubsection);
$section.find('.new-subsection-name-input').focus().select();
var $saveButton = $newSubsection.find('.new-subsection-name-save');
var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
var parent = $(this).parents("section.courseware-section").data("locator");
$saveButton.data('parent', parent);
$saveButton.data('category', $(this).data('category'));
$newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
$cancelButton.bind('click', cancelNewSubsection);
CancelOnEscape($cancelButton);
};
var saveNewSubsection = function (e) {
e.preventDefault();
var parent = $(this).find('.new-subsection-name-save').data('parent');
var category = $(this).find('.new-subsection-name-save').data('category');
var display_name = $(this).find('.new-subsection-name-input').val();
analytics.track('Created a Subsection', {
'course': course_location_analytics,
'display_name': display_name
});
$.postJSON(ModuleUtils.getUpdateUrl(), {
'parent_locator': parent,
'category': category,
'display_name': display_name
},
function(data) {
if (data.locator != undefined) {
location.reload();
}
});
};
var cancelNewSubsection = function (e) {
e.preventDefault();
$(this).parents('li.courseware-subsection').remove();
};
domReady(function() {
// toggling overview section details
$(function() {
if ($('.courseware-section').length > 0) {
$('.toggle-button-sections').addClass('is-shown');
}
});
$('.toggle-button-sections').bind('click', toggleSections);
$('.expand-collapse').bind('click', toggleSubmodules);
$('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
$('.wrapper-alert-announcement').remove();
}));
var $body = $('body');
$body.on('click', '.section-published-date .edit-release-date', editSectionPublishDate);
$body.on('click', '.edit-section-publish-settings .action-save', saveSetSectionScheduleDate);
$body.on('click', '.edit-section-publish-settings .action-cancel', closeModalNew);
$('.new-courseware-section-button').bind('click', addNewSection);
$('.new-subsection-item').bind('click', addNewSubsection);
});
return {
saveSetSectionScheduleDate: saveSetSectionScheduleDate
};
});
...@@ -22,9 +22,7 @@ define(["underscore", "backbone", "gettext", "text!templates/paging-header.under ...@@ -22,9 +22,7 @@ define(["underscore", "backbone", "gettext", "text!templates/paging-header.under
currentPage = collection.currentPage, currentPage = collection.currentPage,
lastPage = collection.totalPages - 1, lastPage = collection.totalPages - 1,
messageHtml = this.messageHtml(); messageHtml = this.messageHtml();
this.$el.html(_.template(paging_header_template, { this.$el.html(_.template(paging_header_template)({ messageHtml: messageHtml}));
messageHtml: messageHtml
}));
this.$(".previous-page-link").toggleClass("is-disabled", currentPage === 0).attr('aria-disabled', currentPage === 0); this.$(".previous-page-link").toggleClass("is-disabled", currentPage === 0).attr('aria-disabled', currentPage === 0);
this.$(".next-page-link").toggleClass("is-disabled", currentPage === lastPage).attr('aria-disabled', currentPage === lastPage); this.$(".next-page-link").toggleClass("is-disabled", currentPage === lastPage).attr('aria-disabled', currentPage === lastPage);
return this; return this;
......
...@@ -27,10 +27,9 @@ define(["js/views/baseview", "underscore", "gettext", "common/js/components/view ...@@ -27,10 +27,9 @@ define(["js/views/baseview", "underscore", "gettext", "common/js/components/view
}, },
confirmDelete: function(e) { confirmDelete: function(e) {
if(e && e.preventDefault) { e.preventDefault(); } if(e && e.preventDefault) { e.preventDefault(); }
var textbook = this.model, collection = this.model.collection; var textbook = this.model;
var msg = new PromptView.Warning({ new PromptView.Warning({
title: _.template( title: _.template(gettext("Delete “<%= name %>”?"))(
gettext("Delete “<%= name %>”?"),
{name: textbook.get('name')} {name: textbook.get('name')}
), ),
message: gettext("Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed."), message: gettext("Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed."),
......
...@@ -18,7 +18,9 @@ function($, Backbone, _, Utils) { ...@@ -18,7 +18,9 @@ function($, Backbone, _, Utils) {
uploadTpl: '#file-upload', uploadTpl: '#file-upload',
initialize: function () { initialize: function () {
_.bindAll(this); _.bindAll(this,
'changeHandler', 'clickHandler', 'xhrResetProgressBar', 'xhrProgressHandler', 'xhrCompleteHandler'
);
this.file = false; this.file = false;
this.render(); this.render();
......
...@@ -29,7 +29,9 @@ function($, Backbone, _, Utils, FileUploader, gettext) { ...@@ -29,7 +29,9 @@ function($, Backbone, _, Utils, FileUploader, gettext) {
}, },
initialize: function () { initialize: function () {
_.bindAll(this); _.bindAll(this,
'importHandler', 'replaceHandler', 'chooseHandler', 'useExistingHandler', 'showError', 'hideError'
);
this.component_locator = this.$el.closest('[data-locator]').data('locator'); this.component_locator = this.$el.closest('[data-locator]').data('locator');
......
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
"There were {strong_start}{num_errors} validation errors{strong_end} while trying to save the course settings in the database.", "There were {strong_start}{num_errors} validation errors{strong_end} while trying to save the course settings in the database.",
num_errors num_errors
), ),
{interpolate: /\{(.+?)\}/g})(
{ {
strong_start:'<strong>', strong_start:'<strong>',
num_errors: num_errors, num_errors: num_errors,
strong_end: '</strong>' strong_end: '</strong>'
}, })%>
{interpolate: /\{(.+?)\}/g})%>
<%= gettext("Please check the following validation feedbacks and reflect them in your course settings:")%></p> <%= gettext("Please check the following validation feedbacks and reflect them in your course settings:")%></p>
</div> </div>
......
...@@ -361,9 +361,14 @@ function (VideoPlayer) { ...@@ -361,9 +361,14 @@ function (VideoPlayer) {
describe('onSeek', function () { describe('onSeek', function () {
beforeEach(function () { beforeEach(function () {
// jasmine.Clock can't be used to fake out debounce with newer versions of underscore
spyOn(_, 'debounce').andCallFake(function (func) {
return function () {
func.apply(this, arguments);
};
});
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
state.videoEl = $('video, iframe'); state.videoEl = $('video, iframe');
jasmine.Clock.useMock();
spyOn(state.videoPlayer, 'duration').andReturn(120); spyOn(state.videoPlayer, 'duration').andReturn(120);
}); });
...@@ -384,9 +389,6 @@ function (VideoPlayer) { ...@@ -384,9 +389,6 @@ function (VideoPlayer) {
spyOn(state.videoPlayer, 'stopTimer'); spyOn(state.videoPlayer, 'stopTimer');
spyOn(state.videoPlayer, 'runTimer'); spyOn(state.videoPlayer, 'runTimer');
state.videoPlayer.seekTo(10); state.videoPlayer.seekTo(10);
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(10); expect(state.videoPlayer.currentTime).toBe(10);
expect(state.videoPlayer.stopTimer).toHaveBeenCalled(); expect(state.videoPlayer.stopTimer).toHaveBeenCalled();
expect(state.videoPlayer.runTimer).toHaveBeenCalled(); expect(state.videoPlayer.runTimer).toHaveBeenCalled();
...@@ -399,9 +401,6 @@ function (VideoPlayer) { ...@@ -399,9 +401,6 @@ function (VideoPlayer) {
state.videoProgressSlider.onSlide( state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 30 } jQuery.Event('slide'), { value: 30 }
); );
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(30); expect(state.videoPlayer.currentTime).toBe(30);
expect(state.videoPlayer.player.seekTo).toHaveBeenCalledWith(30, true); expect(state.videoPlayer.player.seekTo).toHaveBeenCalledWith(30, true);
}); });
...@@ -413,9 +412,6 @@ function (VideoPlayer) { ...@@ -413,9 +412,6 @@ function (VideoPlayer) {
state.videoProgressSlider.onSlide( state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 30 } jQuery.Event('slide'), { value: 30 }
); );
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(30); expect(state.videoPlayer.currentTime).toBe(30);
expect(state.videoPlayer.updatePlayTime).toHaveBeenCalledWith(30, true); expect(state.videoPlayer.updatePlayTime).toHaveBeenCalledWith(30, true);
}); });
...@@ -426,17 +422,11 @@ function (VideoPlayer) { ...@@ -426,17 +422,11 @@ function (VideoPlayer) {
state.videoProgressSlider.onSlide( state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 20 } jQuery.Event('slide'), { value: 20 }
); );
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
state.videoPlayer.pause(); state.videoPlayer.pause();
expect(state.videoPlayer.currentTime).toBe(20); expect(state.videoPlayer.currentTime).toBe(20);
state.videoProgressSlider.onSlide( state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 10 } jQuery.Event('slide'), { value: 10 }
); );
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(10); expect(state.videoPlayer.currentTime).toBe(10);
}); });
......
...@@ -13,7 +13,7 @@ var setupFullScreenModal = function() { ...@@ -13,7 +13,7 @@ var setupFullScreenModal = function() {
"largeALT": smallImageObject.attr('alt'), "largeALT": smallImageObject.attr('alt'),
"largeSRC": largeImageSRC "largeSRC": largeImageSRC
}; };
var html = _.template($("#image-modal-tpl").text(), data); var html = _.template($("#image-modal-tpl").text())(data);
$(this).replaceWith(html); $(this).replaceWith(html);
} }
}); });
......
...@@ -115,7 +115,7 @@ describe "ThreadResponseShowView", -> ...@@ -115,7 +115,7 @@ describe "ThreadResponseShowView", ->
expect(@view.$(".posted-details").text()).not.toMatch("marked as answer") expect(@view.$(".posted-details").text()).not.toMatch("marked as answer")
it "allows a moderator to mark an answer in a question thread", -> it "allows a moderator to mark an answer in a question thread", ->
DiscussionUtil.loadRoles({"Moderator": parseInt(window.user.id)}) DiscussionUtil.loadRoles({"Moderator": [parseInt(window.user.id)]})
@thread.set({ @thread.set({
"thread_type": "question", "thread_type": "question",
"user_id": (parseInt(window.user.id) + 1).toString() "user_id": (parseInt(window.user.id) + 1).toString()
......
class @DiscussionFilter
# TODO: this helper class duplicates functionality in DiscussionThreadListView.filterTopics
# for use with a very similar category dropdown in the New Post form. The two menus' implementations
# should be merged into a single reusable view.
@filterDrop: (e) ->
$drop = $(e.target).parents('.topic-menu-wrapper')
query = $(e.target).val()
$items = $drop.find('.topic-menu-item')
if(query.length == 0)
$items.removeClass('hidden')
return;
$items.addClass('hidden')
$items.each (i) ->
path = $(this).parents(".topic-menu-item").andSelf()
pathTitles = path.children(".topic-title").map((i, elem) -> $(elem).text()).get()
pathText = pathTitles.join(" / ").toLowerCase()
if query.split(" ").every((term) -> pathText.search(term.toLowerCase()) != -1)
$(this).removeClass('hidden')
# show children
$(this).find('.topic-menu-item').removeClass('hidden');
# show parents
$(this).parents('.topic-menu-item').removeClass('hidden');
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
this.threadType = this.model.get('thread_type'); this.threadType = this.model.get('thread_type');
this.topicId = this.model.get('commentable_id'); this.topicId = this.model.get('commentable_id');
this.context = options.context || 'course'; this.context = options.context || 'course';
_.bindAll(this); _.bindAll(this, 'updateHandler', 'cancelHandler');
return this; return this;
}, },
......
...@@ -39,7 +39,7 @@ if Backbone? ...@@ -39,7 +39,7 @@ if Backbone?
@searchAlertCollection.on "add", (searchAlert) => @searchAlertCollection.on "add", (searchAlert) =>
content = _.template( content = _.template(
$("#search-alert-template").html(), $("#search-alert-template").html())(
{'message': searchAlert.attributes.message, 'cid': searchAlert.cid} {'message': searchAlert.attributes.message, 'cid': searchAlert.cid}
) )
@$(".search-alerts").append(content) @$(".search-alerts").append(content)
...@@ -491,7 +491,7 @@ if Backbone? ...@@ -491,7 +491,7 @@ if Backbone?
message = interpolate( message = interpolate(
_.escape(gettext('Show posts by %(username)s.')), _.escape(gettext('Show posts by %(username)s.')),
{"username": {"username":
_.template('<a class="link-jump" href="<%= url %>"><%- username %></a>', { _.template('<a class="link-jump" href="<%= url %>"><%- username %></a>')({
url: DiscussionUtil.urlFor("user_profile", response.users[0].id), url: DiscussionUtil.urlFor("user_profile", response.users[0].id),
username: response.users[0].username username: response.users[0].username
}) })
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
'click .post-topic-button': 'toggleTopicDropdown', 'click .post-topic-button': 'toggleTopicDropdown',
'click .topic-menu-wrapper': 'handleTopicEvent', 'click .topic-menu-wrapper': 'handleTopicEvent',
'click .topic-filter-label': 'ignoreClick', 'click .topic-filter-label': 'ignoreClick',
'keyup .topic-filter-input': this.DiscussionFilter.filterDrop 'keyup .topic-filter-input': 'filterDrop'
}, },
attributes: { attributes: {
...@@ -17,7 +17,9 @@ ...@@ -17,7 +17,9 @@
this.course_settings = options.course_settings; this.course_settings = options.course_settings;
this.currentTopicId = options.topicId; this.currentTopicId = options.topicId;
this.maxNameWidth = 100; this.maxNameWidth = 100;
_.bindAll(this); _.bindAll(this,
'toggleTopicDropdown', 'handleTopicEvent', 'hideTopicDropdown', 'ignoreClick'
);
return this; return this;
}, },
...@@ -34,7 +36,7 @@ ...@@ -34,7 +36,7 @@
render: function() { render: function() {
var context = _.clone(this.course_settings.attributes); var context = _.clone(this.course_settings.attributes);
context.topics_html = this.renderCategoryMap(this.course_settings.get('category_map')); context.topics_html = this.renderCategoryMap(this.course_settings.get('category_map'));
this.$el.html(_.template($('#topic-template').html(), context)); this.$el.html(_.template($('#topic-template').html())(context));
this.dropdownButton = this.$('.post-topic-button'); this.dropdownButton = this.$('.post-topic-button');
this.topicMenu = this.$('.topic-menu-wrapper'); this.topicMenu = this.$('.topic-menu-wrapper');
this.selectedTopic = this.$('.js-selected-topic'); this.selectedTopic = this.$('.js-selected-topic');
...@@ -187,6 +189,38 @@ ...@@ -187,6 +189,38 @@
} }
} }
return name; return name;
},
// TODO: this helper class duplicates functionality in DiscussionThreadListView.filterTopics
// for use with a very similar category dropdown in the New Post form. The two menus' implementations
// should be merged into a single reusable view.
filterDrop: function (e) {
var $drop, $items, query;
$drop = $(e.target).parents('.topic-menu-wrapper');
query = $(e.target).val();
$items = $drop.find('.topic-menu-item');
if (query.length === 0) {
$items.removeClass('hidden');
return;
}
$items.addClass('hidden');
$items.each(function (_index, item) {
var path, pathText, pathTitles;
path = $(item).parents(".topic-menu-item").andSelf();
pathTitles = path.children(".topic-title").map(function (_, elem) {
return $(elem).text();
}).get();
pathText = pathTitles.join(" / ").toLowerCase();
if (query.split(" ").every(function (term) {
return pathText.search(term.toLowerCase()) !== -1;
})) {
$(item).removeClass('hidden');
$(item).find('.topic-menu-item').removeClass('hidden');
$(item).parents('.topic-menu-item').removeClass('hidden');
}
});
} }
}); });
} }
......
...@@ -17,7 +17,7 @@ if Backbone? ...@@ -17,7 +17,7 @@ if Backbone?
mode: @mode, mode: @mode,
form_id: @mode + (if @topicId then "-" + @topicId else "") form_id: @mode + (if @topicId then "-" + @topicId else "")
}) })
@$el.html(_.template($("#new-post-template").html(), context)) @$el.html(_.template($("#new-post-template").html())(context))
threadTypeTemplate = _.template($("#thread-type-template").html()); threadTypeTemplate = _.template($("#thread-type-template").html());
if $('.js-group-select').is(':disabled') if $('.js-group-select').is(':disabled')
$('.group-selector-wrapper').addClass('disabled') $('.group-selector-wrapper').addClass('disabled')
......
...@@ -79,6 +79,9 @@ ...@@ -79,6 +79,9 @@
* underlying server API. * underlying server API.
*/ */
getPage: function () { getPage: function () {
// TODO: this.currentPage is currently returning a function sometimes when it is called.
// It is possible it always did this, but we either need to investigate more, or just wait until
// we replace this code with the pattern library.
return this.currentPage + (this.isZeroIndexed ? 1 : 0); return this.currentPage + (this.isZeroIndexed ? 1 : 0);
}, },
......
...@@ -244,7 +244,7 @@ ...@@ -244,7 +244,7 @@
if (!validateTotalKeyLength(key_field_selectors)) { if (!validateTotalKeyLength(key_field_selectors)) {
$(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding); $(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding);
$(selectors.errorMessage).html( $(selectors.errorMessage).html(
'<p>' + _.template(message_tpl, {limit: MAX_SUM_KEY_LENGTH}) + '</p>' '<p>' + _.template(message_tpl)({limit: MAX_SUM_KEY_LENGTH}) + '</p>'
); );
$(selectors.save).addClass(classes.disabled); $(selectors.save).addClass(classes.disabled);
} else { } else {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"], define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
function($, _, str, SystemFeedbackView) { function($, _, str, SystemFeedbackView) {
str = str || _.str;
var Alert = SystemFeedbackView.extend({ var Alert = SystemFeedbackView.extend({
options: $.extend({}, SystemFeedbackView.prototype.options, { options: $.extend({}, SystemFeedbackView.prototype.options, {
type: "alert" type: "alert"
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"], define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
function($, _, str, SystemFeedbackView) { function($, _, str, SystemFeedbackView) {
str = str || _.str;
var Notification = SystemFeedbackView.extend({ var Notification = SystemFeedbackView.extend({
options: $.extend({}, SystemFeedbackView.prototype.options, { options: $.extend({}, SystemFeedbackView.prototype.options, {
type: "notification", type: "notification",
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"], define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
function($, _, str, SystemFeedbackView) { function($, _, str, SystemFeedbackView) {
str = str || _.str;
var Prompt = SystemFeedbackView.extend({ var Prompt = SystemFeedbackView.extend({
options: $.extend({}, SystemFeedbackView.prototype.options, { options: $.extend({}, SystemFeedbackView.prototype.options, {
type: "prompt", type: "prompt",
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
}, },
render: function () { render: function () {
this.$el.html(_.template(paginatedViewTemplate, {type: this.type})); this.$el.html(_.template(paginatedViewTemplate)({type: this.type}));
this.assign(this.listView, '.' + this.type + '-list'); this.assign(this.listView, '.' + this.type + '-list');
if (this.headerView) { if (this.headerView) {
this.assign(this.headerView, '.' + this.type + '-paging-header'); this.assign(this.headerView, '.' + this.type + '-paging-header');
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
this.$el.removeClass('hidden'); this.$el.removeClass('hidden');
} }
} }
this.$el.html(_.template(paging_footer_template, { this.$el.html(_.template(paging_footer_template)({
current_page: this.collection.getPage(), current_page: this.collection.getPage(),
total_pages: this.collection.totalPages total_pages: this.collection.totalPages
})); }));
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
context, true context, true
); );
} }
this.$el.html(_.template(headerTemplate, { this.$el.html(_.template(headerTemplate)({
message: message, message: message,
srInfo: this.srInfo, srInfo: this.srInfo,
sortableFields: this.collection.sortableFields, sortableFields: this.collection.sortableFields,
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
}, },
render: function() { render: function() {
this.$el.html(_.template(searchFieldTemplate, { this.$el.html(_.template(searchFieldTemplate)({
type: this.type, type: this.type,
searchString: this.collection.searchString, searchString: this.collection.searchString,
searchLabel: this.label searchLabel: this.label
......
...@@ -15,9 +15,6 @@ ...@@ -15,9 +15,6 @@
* by the access view, but doing it here helps keep the * by the access view, but doing it here helps keep the
* utility self-contained. * utility self-contained.
*/ */
if (_.isUndefined(_s)) {
_s = _.str;
}
_.mixin( _s.exports() ); _.mixin( _s.exports() );
utils = (function(){ utils = (function(){
......
<% if (!readOnly) { %> <% if (!readOnly) { %>
<ul class="<%= contentType %>-actions-list"> <ul class="<%= contentType %>-actions-list">
<% _.each(primaryActions, function(action) { print(_.template($('#forum-action-' + action).html(), {})) }) %> <% _.each(primaryActions, function(action) { print(_.template($('#forum-action-' + action).html())({})) }) %>
<li class="actions-item is-visible"> <li class="actions-item is-visible">
<div class="more-wrapper"> <div class="more-wrapper">
<a href="javascript:void(0)" class="action-button action-more" role="button" aria-haspopup="true" aria-controls="action-menu-<%= contentId %>"> <a href="javascript:void(0)" class="action-button action-more" role="button" aria-haspopup="true" aria-controls="action-menu-<%= contentId %>">
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
</a> </a>
<div class="actions-dropdown" id="action-menu-<%= contentType %>" aria-expanded="false"> <div class="actions-dropdown" id="action-menu-<%= contentType %>" aria-expanded="false">
<ul class="actions-dropdown-list"> <ul class="actions-dropdown-list">
<% _.each(secondaryActions, function(action) { print(_.template($('#forum-action-' + action).html(), {})) }) %> <% _.each(secondaryActions, function(action) { print(_.template($('#forum-action-' + action).html())({})) }) %>
</ul> </ul>
</div> </div>
</div> </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="response-body"><%- body %></div> <div class="response-body"><%- body %></div>
<%= <%=
_.template( _.template(
$('#forum-actions').html(), $('#forum-actions').html())(
{ {
contentId: cid, contentId: cid,
contentType: 'comment', contentType: 'comment',
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
<div class="response-header-actions"> <div class="response-header-actions">
<%= <%=
_.template( _.template(
$('#forum-actions').html(), $('#forum-actions').html())(
{ {
contentId: cid, contentId: cid,
contentType: 'response', contentType: 'response',
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<div class="post-header-actions post-extended-content"> <div class="post-header-actions post-extended-content">
<%= <%=
_.template( _.template(
$('#forum-actions').html(), $('#forum-actions').html())(
{ {
contentId: cid, contentId: cid,
contentType: 'post', contentType: 'post',
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
*/ */
var interpolate_ntext = function (singular, plural, count, values) { var interpolate_ntext = function (singular, plural, count, values) {
var text = count === 1 ? singular : plural; var text = count === 1 ? singular : plural;
return _.template(text, values, {interpolate: /\{(.+?)\}/g}); return _.template(text, {interpolate: /\{(.+?)\}/g})(values);
}; };
this.interpolate_ntext = interpolate_ntext; this.interpolate_ntext = interpolate_ntext;
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
* @returns the text with placeholder values filled in * @returns the text with placeholder values filled in
*/ */
var interpolate_text = function (text, values) { var interpolate_text = function (text, values) {
return _.template(text, values, {interpolate: /\{(.+?)\}/g}); return _.template(text, {interpolate: /\{(.+?)\}/g})(values);
}; };
this.interpolate_text = interpolate_text; this.interpolate_text = interpolate_text;
}).call(this, _); }).call(this, _);
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
} }
this.hide(); this.hide();
_.bindAll(this); _.bindAll(this, 'show', 'hide', 'showTooltip', 'moveTooltip', 'hideTooltip', 'click');
this.bindEvents(); this.bindEvents();
}; };
......
!function(e,t){"use strict";var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return"";var n="";while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?"\\s":e.source?e.source:"["+p.escapeRegExp(e)+"]"},f={lt:"<",gt:">",quot:'"',apos:"'",amp:"&"},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u="",a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u==="string")f.push(r[l]);else if(u==="array"){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h('[_.sprintf] property "%s" does not exist',p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!="number")throw new Error(h("[_.sprintf] expecting number but found %s",e(a)));switch(p[8]){case"b":a=a.toString(2);break;case"c":a=t.fromCharCode(a);break;case"d":a=parseInt(a,10);break;case"e":a=p[7]?a.toExponential(p[7]):a.toExponential();break;case"f":a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case"o":a=a.toString(8);break;case"s":a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case"u":a=Math.abs(a);break;case"x":a=a.toString(16);break;case"X":a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?"+"+a:a,v=p[4]?p[4]=="0"?"0":p[4].charAt(1):" ",m=p[6]-t(a).length,d=p[6]?n(v,m):"",f.push(p[5]?a+d:d+a)}}return f.join("")},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error("[_.sprintf] huh?");if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error("[_.sprintf] mixing positional and named placeholders is not (yet) supported");r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:"2.3.0",isBlank:function(e){return e==null&&(e=""),/^\s*$/.test(e)},stripTags:function(e){return e==null?"":t(e).replace(/<\/?[^>]+>/g,"")},capitalize:function(e){return e=e==null?"":t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(".{1,"+n+"}","g")):[e])},clean:function(e){return p.strip(e).replace(/\s+/g," ")},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split("")},swapCase:function(e){return e==null?"":t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?"":t(e).replace(/[&<>"']/g,function(e){return"&"+l[e]+";"})},unescapeHTML:function(e){return e==null?"":t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?"":t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join("")},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===""?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=""),e.join(t)},lines:function(e){return e==null?[]:t(e).split("\n")},reverse:function(e){return p.chars(e).reverse().join("")},startsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?"":(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?"":t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,"-$1").replace(/[-_\s]+/g,"-").toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g," ")).replace(/\s/g,"")},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,"").replace(/_/g," "))},trim:function(e,r){return e==null?"":!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp("^"+r+"+|"+r+"+$","g"),""))},ltrim:function(e,n){return e==null?"":!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp("^"+n+"+"),""))},rtrim:function(e,n){return e==null?"":!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+"+$"),""))},truncate:function(e,n,r){return e==null?"":(e=t(e),r=r||"...",n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return"";e=t(e),n=~~n,r=r!=null?t(r):"...";if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?"A":" "},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,""):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?"":t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=" ";switch(i){case"right":return s=n-e.length,e+o(r,s);case"both":return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,"right")},lrpad:function(e,t,n){return p.pad(e,t,n,"both")},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e=="")return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return"";e=e.toFixed(~~t),r=r||",";var i=e.split("."),s=i[0],o=i[1]?(n||".")+i[1]:"";return s.replace(/(\d)(?=(?:\d{3})+$)/g,"$1"+r)+o},strRight:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return"";e+="",t=t!=null?""+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||", ",n=n||" and ";var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return"";var n="ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź",r="aaaaaaaaceeeeeiiiilnoooooouuuunczz",i=new RegExp(a(n),"g");return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||"-"}),p.dasherize(e.replace(/[^\w\s-]/g,""))},surround:function(e,t){return[t,e,t].join("")},quote:function(e){return p.surround(e,'"')},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return"";n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[--n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(module.exports=p),exports._s=p):typeof define==="function"&&define.amd?define("underscore.string",[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String);
\ No newline at end of file
../../../../node_modules/underscore.string/dist/underscore.string.min.js
\ No newline at end of file
...@@ -508,12 +508,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer): ...@@ -508,12 +508,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
self.q(css='{} .section-name .save-button'.format(parent_css)).first.click() self.q(css='{} .section-name .save-button'.format(parent_css)).first.click()
self.wait_for_ajax() self.wait_for_ajax()
def click_release_date(self):
"""
Open release date edit modal of first section in course outline
"""
self.q(css='div.section-published-date a.edit-release-date').first.click()
def sections(self): def sections(self):
""" """
Returns the sections of this course outline page. Returns the sections of this course outline page.
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
certificates: this.certificates certificates: this.certificates
}; };
this.setResults(_.template(resultsTpl, context)); this.setResults(_.template(resultsTpl)(context));
}, },
renderError: function(error) { renderError: function(error) {
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
render: function () { render: function () {
var user = this.enrollments.user; var user = this.enrollments.user;
this.$el.html(_.template(enrollmentTemplate, { this.$el.html(_.template(enrollmentTemplate)({
user: user, user: user,
enrollments: this.enrollments, enrollments: this.enrollments,
formatDate: function (date) { formatDate: function (date) {
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
}, },
render: function () { render: function () {
this.$el.html(_.template(this.template, { this.$el.html(_.template(this.template)({
enrollment: this.enrollment, enrollment: this.enrollment,
modes: this.modes, modes: this.modes,
reasons: this.reasons, reasons: this.reasons,
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
var memberships = this.model.get('membership'), var memberships = this.model.get('membership'),
discussionTopicID = this.model.get('discussion_topic_id'), discussionTopicID = this.model.get('discussion_topic_id'),
isMember = TeamUtils.isUserMemberOfTeam(memberships, this.context.userInfo.username); isMember = TeamUtils.isUserMemberOfTeam(memberships, this.context.userInfo.username);
this.$el.html(_.template(teamTemplate, { this.$el.html(_.template(teamTemplate)({
courseID: this.context.courseID, courseID: this.context.courseID,
discussionTopicID: discussionTopicID, discussionTopicID: discussionTopicID,
readOnly: !(this.context.userInfo.privileged || isMember), readOnly: !(this.context.userInfo.privileged || isMember),
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
renderTeamMembers: function() { renderTeamMembers: function() {
var view = this; var view = this;
_.each(this.model.get('membership'), function(membership) { _.each(this.model.get('membership'), function(membership) {
view.$('.members-info').append(_.template(teamMemberTemplate, { view.$('.members-info').append(_.template(teamMemberTemplate)({
imageUrl: membership.user.profile_image.image_url_medium, imageUrl: membership.user.profile_image.image_url_medium,
username: membership.user.username, username: membership.user.username,
memberProfileUrl: '/u/' + membership.user.username memberProfileUrl: '/u/' + membership.user.username
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
'span_end': '</a>' 'span_end': '</a>'
} }
); );
self.$el.append(_.template(teamActionsTemplate, {message: message})); self.$el.append(_.template(teamActionsTemplate)({message: message}));
} }
}); });
return this; return this;
......
...@@ -1237,6 +1237,7 @@ base_vendor_js = [ ...@@ -1237,6 +1237,7 @@ base_vendor_js = [
'js/vendor/jquery.cookie.js', 'js/vendor/jquery.cookie.js',
'js/vendor/url.min.js', 'js/vendor/url.min.js',
'js/vendor/underscore-min.js', 'js/vendor/underscore-min.js',
'js/vendor/underscore.string.min.js',
'js/vendor/requirejs/require.js', 'js/vendor/requirejs/require.js',
'js/RequireJS-namespace-undefine.js', 'js/RequireJS-namespace-undefine.js',
'js/vendor/URI.min.js', 'js/vendor/URI.min.js',
......
...@@ -117,7 +117,7 @@ class AuthListWidget extends MemberListWidget ...@@ -117,7 +117,7 @@ class AuthListWidget extends MemberListWidget
# create revoke button and insert it into the row # create revoke button and insert it into the row
label_trans = gettext("Revoke access") label_trans = gettext("Revoke access")
$revoke_btn = $ _.template('<div class="revoke"><i class="icon fa fa-times-circle" aria-hidden="true"></i> <%= label %></div>', {label: label_trans}), $revoke_btn = $ _.template('<div class="revoke"><i class="icon fa fa-times-circle" aria-hidden="true"></i> <%= label %></div>')({label: label_trans}),
class: 'revoke' class: 'revoke'
$revoke_btn.click => $revoke_btn.click =>
@modify_member_access member.email, 'revoke', (error) => @modify_member_access member.email, 'revoke', (error) =>
......
...@@ -62,7 +62,7 @@ class SendEmail ...@@ -62,7 +62,7 @@ class SendEmail
success_message = gettext("Your email was successfully queued for sending. Please note that for large classes, it may take up to an hour (or more, if other courses are simultaneously sending email) to send all emails.") success_message = gettext("Your email was successfully queued for sending. Please note that for large classes, it may take up to an hour (or more, if other courses are simultaneously sending email) to send all emails.")
subject = @$subject.val() subject = @$subject.val()
full_confirm_message = _.template(confirm_message, {subject: subject}) full_confirm_message = _.template(confirm_message)({subject: subject})
if confirm full_confirm_message if confirm full_confirm_message
......
...@@ -73,7 +73,7 @@ class @StudentAdmin ...@@ -73,7 +73,7 @@ class @StudentAdmin
if not unique_student_identifier if not unique_student_identifier
return @$request_response_error_progress.text gettext("Please enter a student email address or username.") return @$request_response_error_progress.text gettext("Please enter a student email address or username.")
error_message = gettext("Error getting student progress url for '<%= student_id %>'. Make sure that the student identifier is spelled correctly.") error_message = gettext("Error getting student progress url for '<%= student_id %>'. Make sure that the student identifier is spelled correctly.")
full_error_message = _.template(error_message, {student_id: unique_student_identifier}) full_error_message = _.template(error_message)({student_id: unique_student_identifier})
$.ajax $.ajax
dataType: 'json' dataType: 'json'
...@@ -97,8 +97,8 @@ class @StudentAdmin ...@@ -97,8 +97,8 @@ class @StudentAdmin
delete_module: false delete_module: false
success_message = gettext("Success! Problem attempts reset for problem '<%= problem_id %>' and student '<%= student_id %>'.") success_message = gettext("Success! Problem attempts reset for problem '<%= problem_id %>' and student '<%= student_id %>'.")
error_message = gettext("Error resetting problem attempts for problem '<%= problem_id %>' and student '<%= student_id %>'. Make sure that the problem and student identifiers are complete and correct.") error_message = gettext("Error resetting problem attempts for problem '<%= problem_id %>' and student '<%= student_id %>'. Make sure that the problem and student identifiers are complete and correct.")
full_success_message = _.template(success_message, {problem_id: problem_to_reset, student_id: unique_student_identifier}) full_success_message = _.template(success_message)({problem_id: problem_to_reset, student_id: unique_student_identifier})
full_error_message = _.template(error_message, {problem_id: problem_to_reset, student_id: unique_student_identifier}) full_error_message = _.template(error_message)({problem_id: problem_to_reset, student_id: unique_student_identifier})
$.ajax $.ajax
dataType: 'json' dataType: 'json'
...@@ -116,7 +116,7 @@ class @StudentAdmin ...@@ -116,7 +116,7 @@ class @StudentAdmin
if not problem_to_reset if not problem_to_reset
return @$request_response_error_grade.text gettext("Please enter a problem location.") return @$request_response_error_grade.text gettext("Please enter a problem location.")
confirm_message = gettext("Delete student '<%= student_id %>'s state on problem '<%= problem_id %>'?") confirm_message = gettext("Delete student '<%= student_id %>'s state on problem '<%= problem_id %>'?")
full_confirm_message = _.template(confirm_message, {student_id: unique_student_identifier, problem_id: problem_to_reset}) full_confirm_message = _.template(confirm_message)({student_id: unique_student_identifier, problem_id: problem_to_reset})
if window.confirm full_confirm_message if window.confirm full_confirm_message
send_data = send_data =
...@@ -124,7 +124,7 @@ class @StudentAdmin ...@@ -124,7 +124,7 @@ class @StudentAdmin
problem_to_reset: problem_to_reset problem_to_reset: problem_to_reset
delete_module: true delete_module: true
error_message = gettext("Error deleting student '<%= student_id %>'s state on problem '<%= problem_id %>'. Make sure that the problem and student identifiers are complete and correct.") error_message = gettext("Error deleting student '<%= student_id %>'s state on problem '<%= problem_id %>'. Make sure that the problem and student identifiers are complete and correct.")
full_error_message = _.template(error_message, {student_id: unique_student_identifier, problem_id: problem_to_reset}) full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset})
$.ajax $.ajax
dataType: 'json' dataType: 'json'
...@@ -148,9 +148,9 @@ class @StudentAdmin ...@@ -148,9 +148,9 @@ class @StudentAdmin
unique_student_identifier: unique_student_identifier unique_student_identifier: unique_student_identifier
problem_to_reset: problem_to_reset problem_to_reset: problem_to_reset
success_message = gettext("Started rescore problem task for problem '<%= problem_id %>' and student '<%= student_id %>'. Click the 'Show Background Task History for Student' button to see the status of the task.") success_message = gettext("Started rescore problem task for problem '<%= problem_id %>' and student '<%= student_id %>'. Click the 'Show Background Task History for Student' button to see the status of the task.")
full_success_message = _.template(success_message, {student_id: unique_student_identifier, problem_id: problem_to_reset}) full_success_message = _.template(success_message)({student_id: unique_student_identifier, problem_id: problem_to_reset})
error_message = gettext("Error starting a task to rescore problem '<%= problem_id %>' for student '<%= student_id %>'. Make sure that the the problem and student identifiers are complete and correct.") error_message = gettext("Error starting a task to rescore problem '<%= problem_id %>' for student '<%= student_id %>'. Make sure that the the problem and student identifiers are complete and correct.")
full_error_message = _.template(error_message, {student_id: unique_student_identifier, problem_id: problem_to_reset}) full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset})
$.ajax $.ajax
dataType: 'json' dataType: 'json'
...@@ -171,7 +171,7 @@ class @StudentAdmin ...@@ -171,7 +171,7 @@ class @StudentAdmin
unique_student_identifier: unique_student_identifier unique_student_identifier: unique_student_identifier
problem_location_str: problem_to_reset problem_location_str: problem_to_reset
error_message = gettext("Error getting task history for problem '<%= problem_id %>' and student '<%= student_id %>'. Make sure that the problem and student identifiers are complete and correct.") error_message = gettext("Error getting task history for problem '<%= problem_id %>' and student '<%= student_id %>'. Make sure that the problem and student identifiers are complete and correct.")
full_error_message = _.template(error_message, {student_id: unique_student_identifier, problem_id: problem_to_reset}) full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset})
$.ajax $.ajax
dataType: 'json' dataType: 'json'
...@@ -293,15 +293,15 @@ class @StudentAdmin ...@@ -293,15 +293,15 @@ class @StudentAdmin
if not problem_to_reset if not problem_to_reset
return @$request_response_error_all.text gettext("Please enter a problem location.") return @$request_response_error_all.text gettext("Please enter a problem location.")
confirm_message = gettext("Reset attempts for all students on problem '<%= problem_id %>'?") confirm_message = gettext("Reset attempts for all students on problem '<%= problem_id %>'?")
full_confirm_message = _.template(confirm_message, {problem_id: problem_to_reset}) full_confirm_message = _.template(confirm_message)({problem_id: problem_to_reset})
if window.confirm full_confirm_message if window.confirm full_confirm_message
send_data = send_data =
all_students: true all_students: true
problem_to_reset: problem_to_reset problem_to_reset: problem_to_reset
success_message = gettext("Successfully started task to reset attempts for problem '<%= problem_id %>'. Click the 'Show Background Task History for Problem' button to see the status of the task.") success_message = gettext("Successfully started task to reset attempts for problem '<%= problem_id %>'. Click the 'Show Background Task History for Problem' button to see the status of the task.")
full_success_message = _.template(success_message, {problem_id: problem_to_reset}) full_success_message = _.template(success_message)({problem_id: problem_to_reset})
error_message = gettext("Error starting a task to reset attempts for all students on problem '<%= problem_id %>'. Make sure that the problem identifier is complete and correct.") error_message = gettext("Error starting a task to reset attempts for all students on problem '<%= problem_id %>'. Make sure that the problem identifier is complete and correct.")
full_error_message = _.template(error_message, {problem_id: problem_to_reset}) full_error_message = _.template(error_message)({problem_id: problem_to_reset})
$.ajax $.ajax
dataType: 'json' dataType: 'json'
...@@ -319,15 +319,15 @@ class @StudentAdmin ...@@ -319,15 +319,15 @@ class @StudentAdmin
if not problem_to_reset if not problem_to_reset
return @$request_response_error_all.text gettext("Please enter a problem location.") return @$request_response_error_all.text gettext("Please enter a problem location.")
confirm_message = gettext("Rescore problem '<%= problem_id %>' for all students?") confirm_message = gettext("Rescore problem '<%= problem_id %>' for all students?")
full_confirm_message = _.template(confirm_message, {problem_id: problem_to_reset}) full_confirm_message = _.template(confirm_message)({problem_id: problem_to_reset})
if window.confirm full_confirm_message if window.confirm full_confirm_message
send_data = send_data =
all_students: true all_students: true
problem_to_reset: problem_to_reset problem_to_reset: problem_to_reset
success_message = gettext("Successfully started task to rescore problem '<%= problem_id %>' for all students. Click the 'Show Background Task History for Problem' button to see the status of the task.") success_message = gettext("Successfully started task to rescore problem '<%= problem_id %>' for all students. Click the 'Show Background Task History for Problem' button to see the status of the task.")
full_success_message = _.template(success_message, {problem_id: problem_to_reset}) full_success_message = _.template(success_message)({problem_id: problem_to_reset})
error_message = gettext("Error starting a task to rescore problem '<%= problem_id %>'. Make sure that the problem identifier is complete and correct.") error_message = gettext("Error starting a task to rescore problem '<%= problem_id %>'. Make sure that the problem identifier is complete and correct.")
full_error_message = _.template(error_message, {problem_id: problem_to_reset}) full_error_message = _.template(error_message)({problem_id: problem_to_reset})
$.ajax $.ajax
dataType: 'json' dataType: 'json'
......
...@@ -1134,7 +1134,7 @@ ...@@ -1134,7 +1134,7 @@
// The main dialog box. // The main dialog box.
dialog = doc.createElement("div"); dialog = doc.createElement("div");
dialog.innerHTML = _.template( dialog.innerHTML = _.template(
document.getElementById("customwmd-prompt-template").innerHTML, { document.getElementById("customwmd-prompt-template").innerHTML)({
title: title, title: title,
uploadFieldClass: (imageUploadHandler ? 'file-upload' : ''), uploadFieldClass: (imageUploadHandler ? 'file-upload' : ''),
urlLabel: urlLabel, urlLabel: urlLabel,
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
}, },
validate: function(attrs){ validate: function(attrs){
if (!_.str.trim(attrs.user_name) && !_.str.trim(attrs.user_email)) { if (!str.trim(attrs.user_name) && !str.trim(attrs.user_email)) {
return gettext('Student username/email field is required and can not be empty. ' + return gettext('Student username/email field is required and can not be empty. ' +
'Kindly fill in username/email and then press "Add to Exception List" button.'); 'Kindly fill in username/email and then press "Add to Exception List" button.');
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
['underscore', 'underscore.string', 'gettext', 'backbone'], ['underscore', 'underscore.string', 'gettext', 'backbone'],
function(_, str, gettext, Backbone) { function(_, str, gettext, Backbone) {
return Backbone.Model.extend({ return Backbone.Model.extend({
idAttribute: 'id', idAttribute: 'id',
...@@ -23,7 +24,7 @@ ...@@ -23,7 +24,7 @@
}, },
validate: function(attrs) { validate: function(attrs) {
if (!_.str.trim(attrs.user)) { if (!str.trim(attrs.user)) {
// A username or email must be provided for certificate invalidation // A username or email must be provided for certificate invalidation
return gettext('Student username/email field is required and can not be empty. ' + return gettext('Student username/email field is required and can not be empty. ' +
'Kindly fill in username/email and then press "Invalidate Certificate" button.'); 'Kindly fill in username/email and then press "Invalidate Certificate" button.');
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
if (this.collection.findWhere({user: user})) { if (this.collection.findWhere({user: user})) {
message = gettext("Certificate of <%= user %> has already been invalidated. Please check your spelling and retry."); // jshint ignore:line message = gettext("Certificate of <%= user %> has already been invalidated. Please check your spelling and retry."); // jshint ignore:line
this.escapeAndShowMessage(_.template(message, {user: user})); this.escapeAndShowMessage(_.template(message)({user: user}));
} }
else if (certificate_invalidation.isValid()) { else if (certificate_invalidation.isValid()) {
var self = this; var self = this;
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
success: function(model) { success: function(model) {
self.collection.add(model); self.collection.add(model);
message = gettext('Certificate has been successfully invalidated for <%= user %>.'); message = gettext('Certificate has been successfully invalidated for <%= user %>.');
self.escapeAndShowMessage(_.template(message, {user: user})); self.escapeAndShowMessage(_.template(message)({user: user}));
}, },
error: function(model, response) { error: function(model, response) {
......
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
if(this.collection.findWhere(model)){ if(this.collection.findWhere(model)){
message = gettext("<%= user %> already in exception list."); message = gettext("<%= user %> already in exception list.");
this.escapeAndShowMessage( this.escapeAndShowMessage(
_.template(message, {user: (user_name || user_email)}) _.template(message)({user: (user_name || user_email)})
); );
} }
else if(certificate_exception.isValid()){ else if(certificate_exception.isValid()){
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
success: this.showSuccess( success: this.showSuccess(
this, this,
true, true,
_.template(message, {user: (user_name || user_email)}) _.template(message)({user: (user_name || user_email)})
), ),
error: this.showError(this) error: this.showError(this)
} }
......
...@@ -43,7 +43,7 @@ var edx = edx || {}; ...@@ -43,7 +43,7 @@ var edx = edx || {};
courseKey: this.courseKey courseKey: this.courseKey
}); });
this.$el.html(_.template(templateHtml, context)); this.$el.html(_.template(templateHtml)(context));
this.trackLinks(); this.trackLinks();
...@@ -69,7 +69,7 @@ var edx = edx || {}; ...@@ -69,7 +69,7 @@ var edx = edx || {};
context.course_key = this.courseKey; context.course_key = this.courseKey;
context.username = this.username; context.username = this.username;
context.platformName = this.$el.data('platform-name'); context.platformName = this.$el.data('platform-name');
providerDiv.html(_.template(templateHtml, context)).removeClass('hidden'); providerDiv.html(_.template(templateHtml)(context)).removeClass('hidden');
}, },
renderError: function () { renderError: function () {
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
var self = this; var self = this;
this.$el.html(this.template({})); this.$el.html(this.template({}));
_.each(this.tabs, function(tabInfo, index) { _.each(this.tabs, function(tabInfo, index) {
var tabEl = $(_.template(tabTemplate, { var tabEl = $(_.template(tabTemplate)({
index: index, index: index,
title: tabInfo.title, title: tabInfo.title,
url: tabInfo.url, url: tabInfo.url,
......
...@@ -97,7 +97,7 @@ var edx = edx || {}; ...@@ -97,7 +97,7 @@ var edx = edx || {};
* @returns {DonationView} * @returns {DonationView}
*/ */
render: function() { render: function() {
var html = _.template($("#donation-tpl").html(), {}); var html = _.template($("#donation-tpl").html())({});
this.$el.html(html); this.$el.html(html);
this.$amount = $("input[name=\"amount\"]", this.$el); this.$amount = $("input[name=\"amount\"]", this.$el);
this.$submit = $(".action-donate", this.$el); this.$submit = $(".action-donate", this.$el);
......
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
fields: html || '', fields: html || '',
}); });
this.$el.html(_.template(this.tpl, data)); this.$el.html(_.template(this.tpl)(data));
this.postRender(); this.postRender();
this.validateCountry(); this.validateCountry();
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
}, },
renderSuccess: function() { renderSuccess: function() {
this.$el.html(_.template(successTpl, { this.$el.html(_.template(successTpl)({
course: this.model.get('course'), course: this.model.get('course'),
dashboard_url: this.context.dashboard_url dashboard_url: this.context.dashboard_url
})); }));
......
...@@ -37,7 +37,9 @@ define([ ...@@ -37,7 +37,9 @@ define([
}); });
afterEach(function () { afterEach(function () {
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
}); });
describe('destroy', function () { describe('destroy', function () {
......
...@@ -16,7 +16,9 @@ define([ ...@@ -16,7 +16,9 @@ define([
}); });
afterEach(function () { afterEach(function () {
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
}); });
describe('destroy', function () { describe('destroy', function () {
......
...@@ -31,7 +31,9 @@ define([ ...@@ -31,7 +31,9 @@ define([
}); });
afterEach(function () { afterEach(function () {
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
}); });
it('should log edx.course.student_notes.viewed event properly', function() { it('should log edx.course.student_notes.viewed event properly', function() {
......
...@@ -46,7 +46,9 @@ define([ ...@@ -46,7 +46,9 @@ define([
}); });
afterEach(function () { afterEach(function () {
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
}); });
it('should scroll to a note, open it and freeze the annotator if its id is part of the url hash', function() { it('should scroll to a note, open it and freeze the annotator if its id is part of the url hash', function() {
......
...@@ -11,7 +11,9 @@ define([ ...@@ -11,7 +11,9 @@ define([
}); });
afterEach(function () { afterEach(function () {
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
}); });
it('can initialize annotator correctly', function() { it('can initialize annotator correctly', function() {
......
...@@ -37,7 +37,9 @@ define([ ...@@ -37,7 +37,9 @@ define([
afterEach(function () { afterEach(function () {
NotesVisibilityFactory.VisibilityDecorator._setVisibility(null); NotesVisibilityFactory.VisibilityDecorator._setVisibility(null);
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
$('.annotator-notice').remove(); $('.annotator-notice').remove();
}); });
......
...@@ -46,7 +46,9 @@ define([ ...@@ -46,7 +46,9 @@ define([
}); });
afterEach(function () { afterEach(function () {
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
}); });
it('does not show the viewer if the editor is opened', function() { it('does not show the viewer if the editor is opened', function() {
......
...@@ -21,7 +21,9 @@ define([ ...@@ -21,7 +21,9 @@ define([
afterEach(function () { afterEach(function () {
VisibilityDecorator._setVisibility(null); VisibilityDecorator._setVisibility(null);
_.invoke(Annotator._instances, 'destroy'); while (Annotator._instances.length > 0) {
Annotator._instances[0].destroy();
}
}); });
it('can initialize Notes if it visibility equals True', function() { it('can initialize Notes if it visibility equals True', function() {
......
...@@ -181,22 +181,7 @@ ...@@ -181,22 +181,7 @@
}, },
'underscore': { 'underscore': {
deps: ['underscore.string'], deps: ['underscore.string'],
exports: '_', exports: '_'
init: function(UnderscoreString) {
/* Mix non-conflicting functions from underscore.string
* (all but include, contains, and reverse) into the
* Underscore namespace. This allows the login, register,
* and password reset templates to render independent of the
* access view.
*/
_.mixin(UnderscoreString.exports());
/* Since the access view is not using RequireJS, we also
* expose underscore.string at _.str, so that the access
* view can perform the mixin on its own.
*/
_.str = UnderscoreString;
}
}, },
'backbone': { 'backbone': {
deps: ['underscore', 'jquery'], deps: ['underscore', 'jquery'],
...@@ -374,7 +359,12 @@ ...@@ -374,7 +359,12 @@
}, },
'js/verify_student/views/step_view': { 'js/verify_student/views/step_view': {
exports: 'edx.verify_student.StepView', exports: 'edx.verify_student.StepView',
deps: [ 'jquery', 'underscore', 'underscore.string', 'backbone', 'gettext' ] deps: [ 'jquery', 'underscore', 'underscore.string', 'backbone', 'gettext' ],
init: function() {
// Set global variables that the payment code is expecting to be defined
window._ = require('underscore');
window._.str = require('underscore.string');
}
}, },
'js/verify_student/views/intro_step_view': { 'js/verify_student/views/intro_step_view': {
exports: 'edx.verify_student.IntroStepView', exports: 'edx.verify_student.IntroStepView',
...@@ -515,12 +505,6 @@ ...@@ -515,12 +505,6 @@
], ],
exports: 'Discussion' exports: 'Discussion'
}, },
'xmodule_js/common_static/coffee/src/discussion/discussion_filter': {
deps: [
'xmodule_js/common_static/coffee/src/discussion/utils'
],
exports: 'DiscussionFilter'
},
'xmodule_js/common_static/coffee/src/discussion/models/discussion_course_settings': { 'xmodule_js/common_static/coffee/src/discussion/models/discussion_course_settings': {
deps: [ deps: [
'xmodule_js/common_static/coffee/src/discussion/utils' 'xmodule_js/common_static/coffee/src/discussion/utils'
...@@ -614,7 +598,6 @@ ...@@ -614,7 +598,6 @@
'URI', 'URI',
'xmodule_js/common_static/coffee/src/discussion/content', 'xmodule_js/common_static/coffee/src/discussion/content',
'xmodule_js/common_static/coffee/src/discussion/discussion', 'xmodule_js/common_static/coffee/src/discussion/discussion',
'xmodule_js/common_static/coffee/src/discussion/discussion_filter',
'xmodule_js/common_static/coffee/src/discussion/utils', 'xmodule_js/common_static/coffee/src/discussion/utils',
'xmodule_js/common_static/coffee/src/discussion/models/discussion_course_settings', 'xmodule_js/common_static/coffee/src/discussion/models/discussion_course_settings',
'xmodule_js/common_static/coffee/src/discussion/models/discussion_user', 'xmodule_js/common_static/coffee/src/discussion/models/discussion_user',
......
...@@ -8,7 +8,6 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/ ...@@ -8,7 +8,6 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
describe( 'edx.verify_student.ReverifyView', function() { describe( 'edx.verify_student.ReverifyView', function() {
var TEMPLATES = [ var TEMPLATES = [
"reverify",
"webcam_photo", "webcam_photo",
"image_input", "image_input",
"error", "error",
......
...@@ -35,15 +35,11 @@ var StaffDebug = (function (){ ...@@ -35,15 +35,11 @@ var StaffDebug = (function (){
url: get_url(action.method), url: get_url(action.method),
data: pdata, data: pdata,
success: function(data){ success: function(data){
var text = _.template( var text = _.template(action.success_msg, {interpolate: /\{(.+?)\}/g})(
action.success_msg, {user: data.student}
{user: data.student},
{interpolate: /\{(.+?)\}/g}
); );
var html = _.template( var html = _.template('<p id="idash_msg" class="success">{text}</p>', {interpolate: /\{(.+?)\}/g})(
'<p id="idash_msg" class="success">{text}</p>', {text: text}
{text: text},
{interpolate: /\{(.+?)\}/g}
); );
$("#result_"+sanitized_string(action.locationName)).html(html); $("#result_"+sanitized_string(action.locationName)).html(html);
}, },
...@@ -54,18 +50,14 @@ var StaffDebug = (function (){ ...@@ -54,18 +50,14 @@ var StaffDebug = (function (){
} catch(e) { } catch(e) {
response_json = { error: gettext('Unknown Error Occurred.') }; response_json = { error: gettext('Unknown Error Occurred.') };
} }
var text = _.template( var text = _.template('{error_msg} {error}', {interpolate: /\{(.+?)\}/g})(
'{error_msg} {error}',
{ {
error_msg: action.error_msg, error_msg: action.error_msg,
error: response_json.error error: response_json.error
}, }
{interpolate: /\{(.+?)\}/g}
); );
var html = _.template( var html = _.template('<p id="idash_msg" class="error">{text}</p>', {interpolate: /\{(.+?)\}/g})(
'<p id="idash_msg" class="error">{text}</p>', {text: text}
{text: text},
{interpolate: /\{(.+?)\}/g}
); );
$("#result_"+sanitized_string(action.locationName)).html(html); $("#result_"+sanitized_string(action.locationName)).html(html);
......
...@@ -84,7 +84,7 @@ var edx = edx || {}; ...@@ -84,7 +84,7 @@ var edx = edx || {};
}, },
render: function() { render: function() {
this.$el.html(_.template($('#account-tpl').html(), {})); this.$el.html(_.template($('#account-tpl').html())({}));
this.$email = $('#new-email', this.$el); this.$email = $('#new-email', this.$el);
this.$password = $('#password', this.$el); this.$password = $('#password', this.$el);
this.$emailStatus = $('#new-email-status', this.$el); this.$emailStatus = $('#new-email-status', this.$el);
...@@ -113,7 +113,7 @@ var edx = edx || {}; ...@@ -113,7 +113,7 @@ var edx = edx || {};
event.preventDefault(); event.preventDefault();
this.clearStatus(); this.clearStatus();
self = this; var self = this;
$.ajax({ $.ajax({
url: 'password', url: 'password',
type: 'POST', type: 'POST',
......
...@@ -19,10 +19,6 @@ ...@@ -19,10 +19,6 @@
function($, utility, _, _s, Backbone, LoginModel, PasswordResetModel, RegisterModel, LoginView, function($, utility, _, _s, Backbone, LoginModel, PasswordResetModel, RegisterModel, LoginView,
PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView) { PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView) {
if (_.isUndefined(_s)) {
_s = _.str;
}
return Backbone.View.extend({ return Backbone.View.extend({
tpl: '#access-tpl', tpl: '#access-tpl',
events: { events: {
...@@ -89,7 +85,7 @@ ...@@ -89,7 +85,7 @@
}, },
render: function() { render: function() {
$(this.el).html( _.template( this.tpl, { $(this.el).html( _.template(this.tpl)({
mode: this.activeForm mode: this.activeForm
})); }));
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
render: function( html ) { render: function( html ) {
var fields = html || ''; var fields = html || '';
$(this.el).html( _.template( this.tpl, { $(this.el).html( _.template(this.tpl)({
fields: fields fields: fields
})); }));
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
data[i].errorMessages = this.escapeStrings( data[i].errorMessages ); data[i].errorMessages = this.escapeStrings( data[i].errorMessages );
} }
html.push( _.template( fieldTpl, $.extend( data[i], { html.push( _.template(fieldTpl)($.extend( data[i], {
form: this.formType, form: this.formType,
requiredStr: this.requiredStr requiredStr: this.requiredStr
}) ) ); }) ) );
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
}, },
render: function() { render: function() {
$(this.el).html( _.template( this.tpl, { $(this.el).html(_.template(this.tpl)({
hintedProvider: this.hintedProvider hintedProvider: this.hintedProvider
})); }));
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
}, },
render: function() { render: function() {
$(this.el).html( _.template( this.tpl, { $(this.el).html(_.template(this.tpl)({
// We pass the context object to the template so that // We pass the context object to the template so that
// we can perform variable interpolation using sprintf // we can perform variable interpolation using sprintf
providers: this.providers, providers: this.providers,
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
render: function( html ) { render: function( html ) {
var fields = html || ''; var fields = html || '';
$(this.el).html( _.template( this.tpl, { $(this.el).html(_.template(this.tpl)({
// We pass the context object to the template so that // We pass the context object to the template so that
// we can perform variable interpolation using sprintf // we can perform variable interpolation using sprintf
context: { context: {
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
render: function( html ) { render: function( html ) {
var fields = html || ''; var fields = html || '';
$(this.el).html( _.template( this.tpl, { $(this.el).html(_.template(this.tpl)({
/* We pass the context object to the template so that /* We pass the context object to the template so that
* we can perform variable interpolation using sprintf * we can perform variable interpolation using sprintf
*/ */
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
}, },
render: function () { render: function () {
this.$el.html(_.template(accountSettingsTemplate, { this.$el.html(_.template(accountSettingsTemplate)({
sections: this.options.sectionsData sections: this.options.sectionsData
})); }));
return this; return this;
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
}, },
render: function () { render: function () {
this.$el.html(_.template(learnerProfileTemplate, { this.$el.html(_.template(learnerProfileTemplate)({
username: this.options.accountSettingsModel.get('username'), username: this.options.accountSettingsModel.get('username'),
ownProfile: this.options.ownProfile, ownProfile: this.options.ownProfile,
showFullProfile: this.showFullProfile() showFullProfile: this.showFullProfile()
......
...@@ -21,8 +21,7 @@ ...@@ -21,8 +21,7 @@
}, },
render: function() { render: function() {
var renderedHtml = _.template( var renderedHtml = _.template($( '#error-tpl' ).html())(
$( '#error-tpl' ).html(),
{ {
errorTitle: this.model.get( 'errorTitle' ), errorTitle: this.model.get( 'errorTitle' ),
errorMsg: this.model.get( 'errorMsg' ) errorMsg: this.model.get( 'errorMsg' )
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
}, },
render: function() { render: function() {
var renderedHtml = _.template( $( this.template ).html(), {} ); var renderedHtml = _.template( $( this.template ).html())({});
$( this.el ).html( renderedHtml ); $( this.el ).html( renderedHtml );
// Set the submit button to disabled by default // Set the submit button to disabled by default
......
...@@ -40,8 +40,7 @@ ...@@ -40,8 +40,7 @@
}, },
render: function() { render: function() {
var renderedTemplate = _.template( var renderedTemplate = _.template($( this.templateId ).html())(
$( this.templateId ).html(),
{ {
courseKey: this.courseKey, courseKey: this.courseKey,
platformName: this.platformName platformName: this.platformName
......
/*global jQuery, _, Backbone, gettext */
/** /**
* Base view for defining steps in the payment/verification flow. * Base view for defining steps in the payment/verification flow.
* *
...@@ -8,7 +10,7 @@ ...@@ -8,7 +10,7 @@
*/ */
var edx = edx || {}; var edx = edx || {};
(function( $, _, _s, Backbone, gettext ) { (function( $, _, Backbone, gettext ) {
'use strict'; 'use strict';
edx.verify_student = edx.verify_student || {}; edx.verify_student = edx.verify_student || {};
...@@ -20,9 +22,9 @@ ...@@ -20,9 +22,9 @@
/* Mix non-conflicting functions from underscore.string /* Mix non-conflicting functions from underscore.string
* (all but include, contains, and reverse) into the * (all but include, contains, and reverse) into the
* Underscore namespace * Underscore namespace.
*/ */
_.mixin( _s.exports() ); _.mixin(_.str.exports());
}, },
render: function() { render: function() {
...@@ -33,7 +35,7 @@ ...@@ -33,7 +35,7 @@
this.updateContext( this.templateContext() ).done( this.updateContext( this.templateContext() ).done(
function( templateContext ) { function( templateContext ) {
// Render the template into the DOM // Render the template into the DOM
$( this.el ).html( _.template( templateHtml, templateContext ) ); $( this.el ).html( _.template( templateHtml)( templateContext ) );
// Allow subclasses to install custom event handlers // Allow subclasses to install custom event handlers
this.postRender(); this.postRender();
...@@ -101,4 +103,4 @@ ...@@ -101,4 +103,4 @@
}); });
})( jQuery, _, _.str, Backbone, gettext ); })( jQuery, _, Backbone, gettext );
...@@ -242,8 +242,7 @@ ...@@ -242,8 +242,7 @@
this.setSubmitButtonEnabled( false ); this.setSubmitButtonEnabled( false );
// Load the template for the webcam into the DOM // Load the template for the webcam into the DOM
renderedHtml = _.template( renderedHtml = _.template($( this.template ).html())(
$( this.template ).html(),
{ backendName: this.backend.name } { backendName: this.backend.name }
); );
$( this.el ).html( renderedHtml ); $( this.el ).html( renderedHtml );
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
if (_.isUndefined(this.message) || _.isNull(this.message)) { if (_.isUndefined(this.message) || _.isNull(this.message)) {
this.$el.html(''); this.$el.html('');
} else { } else {
this.$el.html(_.template(messageBannerTemplate, _.extend(this.options, { this.$el.html(_.template(messageBannerTemplate)(_.extend(this.options, {
message: this.message message: this.message
}))); })));
} }
......
...@@ -66,6 +66,7 @@ ...@@ -66,6 +66,7 @@
'jquery.url': 'empty:', 'jquery.url': 'empty:',
'backbone': 'empty:', 'backbone': 'empty:',
'underscore': 'empty:', 'underscore': 'empty:',
'underscore.string': 'empty:',
'logger': 'empty:', 'logger': 'empty:',
'utility': 'empty:', 'utility': 'empty:',
'URI': 'empty:', 'URI': 'empty:',
......
...@@ -18,7 +18,12 @@ ...@@ -18,7 +18,12 @@
} }
}; };
defineDependency("jQuery", "jquery"); defineDependency("jQuery", "jquery");
defineDependency("_", "underscore"); defineDependency("s", "underscore.string");
// Underscore.string no longer installs itself directly on "_". For compatibility with existing
// code, add it to "_" with its previous name.
if (window._ && window.s) {
window._.str = window.s;
}
defineDependency("gettext", "gettext"); defineDependency("gettext", "gettext");
defineDependency("Logger", "logger"); defineDependency("Logger", "logger");
defineDependency("URI", "URI"); defineDependency("URI", "URI");
......
...@@ -19,7 +19,6 @@ from django.utils.translation import ugettext as _ ...@@ -19,7 +19,6 @@ from django.utils.translation import ugettext as _
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script> <script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<script src="${static.url('js/commerce/credit.js')}"></script> <script src="${static.url('js/commerce/credit.js')}"></script>
<script src="${static.url('js/commerce/views/receipt_view.js')}"></script> <script src="${static.url('js/commerce/views/receipt_view.js')}"></script>
......
...@@ -254,13 +254,13 @@ from xmodule.tabs import CourseTabList ...@@ -254,13 +254,13 @@ from xmodule.tabs import CourseTabList
htmlStr = "${_('An error has occurred.')}"; htmlStr = "${_('An error has occurred.')}";
% if settings.FEEDBACK_SUBMISSION_EMAIL: % if settings.FEEDBACK_SUBMISSION_EMAIL:
htmlStr += " " + _.template( htmlStr += " " + _.template(
"${_('Please {link_start}send us e-mail{link_end}.')}", "${_('Please {link_start}send us e-mail{link_end}.')}", {interpolate: /\{(.+?)\}/g})(
{link_start: '<a href="#" id="feedback_email">', link_end: '</a>'}, {link_start: '<a href="#" id="feedback_email">', link_end: '</a>'}
{interpolate: /\{(.+?)\}/g}) );
% else: % else:
// If no email is configured, we can't do much other than // If no email is configured, we can't do much other than
// ask the user to try again later // ask the user to try again later
htmlStr += "${_('Please try again later.')}"; htmlStr += " " + "${_('Please try again later.')}";
% endif % endif
$("#feedback_error").html(htmlStr).stop().css("display", "block"); $("#feedback_error").html(htmlStr).stop().css("display", "block");
% if settings.FEEDBACK_SUBMISSION_EMAIL: % if settings.FEEDBACK_SUBMISSION_EMAIL:
......
...@@ -18,7 +18,6 @@ from django.utils.translation import ugettext as _ ...@@ -18,7 +18,6 @@ from django.utils.translation import ugettext as _
% endfor % endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='incourse_reverify'/> <%static:js group='incourse_reverify'/>
</%block> </%block>
......
...@@ -41,7 +41,6 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView ...@@ -41,7 +41,6 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
% endfor % endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='verify_student'/> <%static:js group='verify_student'/>
</%block> </%block>
......
...@@ -16,7 +16,6 @@ from django.utils.translation import ugettext as _ ...@@ -16,7 +16,6 @@ from django.utils.translation import ugettext as _
% endfor % endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='reverify'/> <%static:js group='reverify'/>
</%block> </%block>
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
"edx-pattern-library": "0.10.4", "edx-pattern-library": "0.10.4",
"requirejs": "~2.1.22", "requirejs": "~2.1.22",
"uglify-js": "2.4.24", "uglify-js": "2.4.24",
"underscore": "1.4.4" "underscore": "~1.8.3",
"underscore.string": "~3.3.4"
}, },
"devDependencies": { "devDependencies": {
"jshint": "^2.7.0", "jshint": "^2.7.0",
......
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