Commit 83fbdc2a by polesye

TNL-481: Add RequireJS Optimizer.

parent 6bb878af
......@@ -325,8 +325,7 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
EMBARGO_SITE_REDIRECT_URL = None
############################### Pipeline #######################################
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
STATICFILES_STORAGE = 'cms.lib.django_require.staticstorage.OptimizedCachedRequireJsStorage'
from rooted_paths import rooted_glob
......@@ -457,6 +456,34 @@ STATICFILES_IGNORE_PATTERNS = (
PIPELINE_YUI_BINARY = 'yui-compressor'
################################# DJANGO-REQUIRE ###############################
# The baseUrl to pass to the r.js optimizer, relative to STATIC_ROOT.
REQUIRE_BASE_URL = "./"
# The name of a build profile to use for your project, relative to REQUIRE_BASE_URL.
# A sensible value would be 'app.build.js'. Leave blank to use the built-in default build profile.
# Set to False to disable running the default profile (e.g. if only using it to build Standalone
# Modules)
REQUIRE_BUILD_PROFILE = "build.js"
# The name of the require.js script used by your project, relative to REQUIRE_BASE_URL.
REQUIRE_JS = "js/vendor/require.js"
# A dictionary of standalone modules to build with almond.js.
REQUIRE_STANDALONE_MODULES = {}
# Whether to run django-require in debug mode.
REQUIRE_DEBUG = False
# A tuple of files to exclude from the compilation result of r.js.
REQUIRE_EXCLUDE = ("build.txt",)
# The execution environment in which to run r.js: auto, node or rhino.
# auto will autodetect the environment and make use of node if available and rhino if not.
# It can also be a path to a custom class that subclasses require.environments.Environment and defines some "args" function that returns a list with the command arguments to execute.
REQUIRE_ENVIRONMENT = "node"
################################# CELERY ######################################
# Message configuration
......@@ -563,6 +590,7 @@ INSTALLED_APPS = (
'pipeline',
'staticfiles',
'static_replace',
'require',
# comment common
'django_comment_common',
......
"""
:class:`~django_require.staticstorage.OptimizedCachedRequireJsStorage`
"""
from pipeline.storage import PipelineCachedStorage
from require.storage import OptimizedFilesMixin
class OptimizedCachedRequireJsStorage(OptimizedFilesMixin, PipelineCachedStorage):
"""
Custom storage backend that is used by Django-require.
"""
pass
(function () {
'use strict';
var getModule = function (moduleName, excludeCommonDeps) {
var module = {
name: moduleName
};
if (excludeCommonDeps) {
module.exclude = ['js/factories/common_deps'];
}
return module;
};
var getModulesList = function (modules) {
var result = [getModule('js/factories/common_deps')];
return result.concat(modules.map(function (moduleName) {
return getModule(moduleName, true);
}));
};
return {
/**
* List the modules that will be optimized. All their immediate and deep
* dependencies will be included in the module's file when the build is
* done.
*/
modules: getModulesList([
'js/factories/asset_index',
'js/factories/base',
'js/factories/checklists',
'js/factories/container',
'js/factories/course',
'js/factories/course_create_rerun',
'js/factories/course_info',
'js/factories/edit_tabs',
'js/factories/export',
'js/factories/group_configurations',
'js/factories/import',
'js/factories/index',
'js/factories/login',
'js/factories/manage_users',
'js/factories/outline',
'js/factories/register',
'js/factories/settings',
'js/factories/settings_advanced',
'js/factories/settings_graders',
'js/factories/textbooks'
]),
/**
* By default all the configuration for optimization happens from the command
* line or by properties in the config file, and configuration that was
* passed to requirejs as part of the app's runtime "main" JS file is *not*
* considered. However, if you prefer the "main" JS file configuration
* to be read for the build so that you do not have to duplicate the values
* in a separate configuration, set this property to the location of that
* main JS file. The first requirejs({}), require({}), requirejs.config({}),
* or require.config({}) call found in that file will be used.
* As of 2.1.10, mainConfigFile can be an array of values, with the last
* value's config take precedence over previous values in the array.
*/
mainConfigFile: 'require-config.js',
/**
* Set paths for modules. If relative paths, set relative to baseUrl above.
* If a special value of "empty:" is used for the path value, then that
* acts like mapping the path to an empty file. It allows the optimizer to
* resolve the dependency to path, but then does not include it in the output.
* Useful to map module names that are to resources on a CDN or other
* http: URL when running in the browser and during an optimization that
* file should be skipped because it has no dependencies.
*/
paths: {
'gettext': 'empty:',
'xmodule': 'empty:',
'mathjax': 'empty:',
'tender': 'empty:',
'youtube': 'empty:'
},
/**
* If shim config is used in the app during runtime, duplicate the config
* here. Necessary if shim config is used, so that the shim's dependencies
* are included in the build. Using "mainConfigFile" is a better way to
* pass this information though, so that it is only listed in one place.
* However, if mainConfigFile is not an option, the shim config can be
* inlined in the build config.
*/
shim: {
'xmodule': {
deps: [
'jquery', 'underscore', 'mathjax', 'codemirror', 'tinymce',
'jquery.tinymce', 'jquery.qtip', 'jquery.scrollTo', 'jquery.flot',
'jquery.cookie', 'utility'
]
}
},
/**
* Introduced in 2.1.2: If using "dir" for an output directory, normally the
* optimize setting is used to optimize the build bundles (the "modules"
* section of the config) and any other JS file in the directory. However, if
* the non-build bundle JS files will not be loaded after a build, you can
* skip the optimization of those files, to speed up builds. Set this value
* to true if you want to skip optimizing those other non-build bundle JS
* files.
*/
skipDirOptimize: true,
/**
* When the optimizer copies files from the source location to the
* destination directory, it will skip directories and files that start
* with a ".". If you want to copy .directories or certain .files, for
* instance if you keep some packages in a .packages directory, or copy
* over .htaccess files, you can set this to null. If you want to change
* the exclusion rules, change it to a different regexp. If the regexp
* matches, it means the directory will be excluded. This used to be
* called dirExclusionRegExp before the 1.0.2 release.
* As of 1.0.3, this value can also be a string that is converted to a
* RegExp via new RegExp().
*/
fileExclusionRegExp: /^\.|spec/,
/**
* Allow CSS optimizations. Allowed values:
* - "standard": @import inlining and removal of comments, unnecessary
* whitespace and line returns.
* Removing line returns may have problems in IE, depending on the type
* of CSS.
* - "standard.keepLines": like "standard" but keeps line returns.
* - "none": skip CSS optimizations.
* - "standard.keepComments": keeps the file comments, but removes line
* returns. (r.js 1.0.8+)
* - "standard.keepComments.keepLines": keeps the file comments and line
* returns. (r.js 1.0.8+)
* - "standard.keepWhitespace": like "standard" but keeps unnecessary whitespace.
*/
optimizeCss: 'none',
/**
* How to optimize all the JS files in the build output directory.
* Right now only the following values are supported:
* - "uglify": Uses UglifyJS to minify the code.
* - "uglify2": Uses UglifyJS2.
* - "closure": Uses Google's Closure Compiler in simple optimization
* mode to minify the code. Only available if REQUIRE_ENVIRONMENT is "rhino" (the default).
* - "none": No minification will be done.
*/
optimize: 'uglify2',
/**
* Sets the logging level. It is a number:
* TRACE: 0,
* INFO: 1,
* WARN: 2,
* ERROR: 3,
* SILENT: 4
* Default is 0.
*/
logLevel: 4
};
} ())
define([
'jquery', 'js/collections/asset', 'js/views/assets', 'jquery.fileupload'
], function($, AssetCollection, AssetsView) {
'use strict';
return function (assetCallbackUrl) {
var assets = new AssetCollection(),
assetsView;
assets.url = assetCallbackUrl;
assetsView = new AssetsView({collection: assets, el: $('.assets-wrapper')});
assetsView.render();
};
});
define(['js/base', 'coffee/src/main', 'coffee/src/logger', 'datepair', 'accessibility',
'ieshim', 'tooltip_manager']);
define([
'jquery', 'js/collections/checklist', 'js/views/checklist'
], function($, ChecklistCollection, ChecklistView) {
'use strict';
return function (handlerUrl) {
var checklistCollection = new ChecklistCollection(),
editor;
checklistCollection.url = handlerUrl;
editor = new ChecklistView({
el: $('.course-checklists'),
collection: checklistCollection
});
checklistCollection.fetch({reset: true});
};
});
define(['domReady!', 'jquery', 'backbone', 'underscore', 'gettext']);
define([
'jquery', 'js/models/xblock_info', 'js/views/pages/container',
'js/collections/component_template', 'xmodule', 'coffee/src/main',
'xblock/cms.runtime.v1'
],
function($, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
'use strict';
return function (componentTemplates, XBlockInfoJson, action, isUnitPage) {
var templates = new ComponentTemplates(componentTemplates, {parse: true}),
mainXBlockInfo = new XBlockInfo(XBlockInfoJson, {parse: true});
xmoduleLoader.done(function () {
var view = new ContainerPage({
el: $('#content'),
model: mainXBlockInfo,
action: action,
templates: templates,
isUnitPage: isUnitPage
});
view.render();
});
};
});
define(['js/models/course'], function(Course) {
'use strict';
return function (courseInfo) {
window.course = new Course(courseInfo);
}
});
define(['jquery', 'jquery.form', 'js/views/course_rerun'], function ($) {
'use strict';
return function () {};
});
define([
'jquery', 'js/collections/course_update', 'js/models/module_info',
'js/models/course_info', 'js/views/course_info_edit'
], function($, CourseUpdateCollection, ModuleInfoModel, CourseInfoModel, CourseInfoEditView) {
'use strict';
return function (updatesUrl, handoutsLocator, baseAssetUrl) {
var course_updates = new CourseUpdateCollection(),
course_handouts, editor;
course_updates.url = updatesUrl;
course_updates.fetch({reset: true});
course_handouts = new ModuleInfoModel({
id: handoutsLocator
});
editor = new CourseInfoEditView({
el: $('.main-wrapper'),
model : new CourseInfoModel({
updates : course_updates,
base_asset_url : baseAssetUrl,
handouts : course_handouts
})
});
editor.render();
};
});
define([
'js/models/explicit_url', 'coffee/src/views/tabs', 'xmodule', 'coffee/src/main', 'xblock/cms.runtime.v1'
], function (TabsModel, TabsEditView, xmoduleLoader) {
'use strict';
return function (courseLocation, explicitUrl) {
xmoduleLoader.done(function () {
var model = new TabsModel({
id: courseLocation,
explicit_url: explicitUrl
}),
editView;
editView = new TabsEditView({
el: $('.tab-list'),
model: model,
mast: $('.wrapper-mast')
});
});
};
});
define(['gettext', 'js/views/feedback_prompt'], function(gettext, PromptView) {
'use strict';
return function (hasUnit, editUnitUrl, courseHomeUrl, errMsg) {
var dialog;
if(hasUnit) {
dialog = new PromptView({
title: gettext('There has been an error while exporting.'),
message: gettext('There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages.'),
intent: 'error',
actions: {
primary: {
text: gettext('Correct failed component'),
click: function(view) {
view.hide();
document.location = editUnitUrl;
}
},
secondary: {
text: gettext('Return to Export'),
click: function(view) {
view.hide();
}
}
}
});
} else {
var msg = '<p>' + gettext('There has been a failure to export your course to XML. Unfortunately, we do not have specific enough information to assist you in identifying the failed component. It is recommended that you inspect your courseware to identify any components in error and try again.') + '</p><p>' + gettext('The raw error message is:') + '</p>' + errMsg;
dialog = new PromptView({
title: gettext('There has been an error with your export.'),
message: msg,
intent: 'error',
actions: {
primary: {
text: gettext('Yes, take me to the main course page'),
click: function(view) {
view.hide();
document.location = courseHomeUrl;
}
},
secondary: {
text: gettext('Cancel'),
click: function(view) {
view.hide();
}
}
}
});
}
// The CSS animation for the dialog relies on the 'js' class
// being on the body. This happens after this JavaScript is executed,
// causing a 'bouncing' of the dialog after it is initially shown.
// As a workaround, add this class first.
$('body').addClass('js');
dialog.show();
};
});
define([
'js/collections/group_configuration', 'js/views/pages/group_configurations'
], function(GroupConfigurationCollection, GroupConfigurationsPage) {
'use strict';
return function (configurations, groupConfigurationUrl, courseOutlineUrl) {
var collection = new GroupConfigurationCollection(configurations, { parse: true }),
configurationsPage;
collection.url = groupConfigurationUrl;
collection.outlineUrl = courseOutlineUrl;
configurationsPage = new GroupConfigurationsPage({
el: $('#content'),
collection: collection
}).render();
};
});
define([
'js/views/import', 'jquery', 'gettext', 'jquery.fileupload', 'jquery.cookie'
], function(CourseImport, $, gettext) {
'use strict';
return function (feedbackUrl) {
var bar = $('.progress-bar'),
fill = $('.progress-fill'),
submitBtn = $('.submit-button'),
chooseBtn = $('.choose-file-button'),
defaults = [
gettext('There was an error during the upload process.') + '\n',
gettext('There was an error while unpacking the file.') + '\n',
gettext('There was an error while verifying the file you submitted.') + '\n',
gettext('There was an error while importing the new course to our database.') + '\n'
],
// Display the status of last file upload on page load
lastFileUpload = $.cookie('lastfileupload'),
file;
if (lastFileUpload){
CourseImport.getAndStartUploadFeedback(feedbackUrl.replace('fillerName', lastFileUpload), lastFileUpload);
}
$('#fileupload').fileupload({
dataType: 'json',
type: 'POST',
maxChunkSize: 20 * 1000000, // 20 MB
autoUpload: false,
add: function(e, data) {
CourseImport.clearImportDisplay();
submitBtn.unbind('click');
file = data.files[0];
if (file.name.match(/tar\.gz$/)) {
submitBtn.click(function(event){
event.preventDefault();
$.cookie('lastfileupload', file.name);
submitBtn.hide();
CourseImport.startUploadFeedback();
data.submit().complete(function(result, textStatus, xhr) {
window.onbeforeunload = null;
if (xhr.status != 200) {
var serverMsg, errMsg, stage;
try{
serverMsg = $.parseJSON(result.responseText);
} catch (e) {
return;
}
errMsg = serverMsg.hasOwnProperty('ErrMsg') ? serverMsg.ErrMsg : '' ;
if (serverMsg.hasOwnProperty('Stage')) {
stage = Math.abs(serverMsg.Stage);
CourseImport.stageError(stage, defaults[stage] + errMsg);
}
else {
alert(gettext('Your import has failed.') + '\n\n' + errMsg);
}
chooseBtn.html(gettext('Choose new file')).show();
bar.hide();
}
CourseImport.stopGetStatus = true;
chooseBtn.html(gettext('Choose new file')).show();
bar.hide();
});
});
} else {
data.files = [];
}
},
progressall: function(e, data){
var percentInt = data.loaded / data.total * 100,
percentVal = parseInt(percentInt, 10) + '%',
doneAt;
// Firefox makes ProgressEvent.loaded equal ProgressEvent.total only
// after receiving a response from the server (see Mozilla bug 637002),
// so for Firefox we jump the gun a little.
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
doneAt = 95;
} else {
doneAt = 99;
}
if (percentInt >= doneAt) {
bar.hide();
// Start feedback with delay so that current stage of import properly updates in session
setTimeout(
function () { CourseImport.startServerFeedback(feedbackUrl.replace('fillerName', file.name));},
3000
);
} else {
bar.show();
fill.width(percentVal).html(percentVal);
}
},
done: function(event, data){
bar.hide();
window.onbeforeunload = null;
CourseImport.displayFinishedImport();
},
start: function(event) {
window.onbeforeunload = function() {
return gettext('Your import is in progress; navigating away will abort it.');
};
},
sequentialUploads: true,
notifyOnError: false
});
};
});
define(['jquery.form', 'js/index'], function() {
'use strict';
return function () {
// showing/hiding creation rights UI
$('.show-creationrights').click(function(e) {
e.preventDefault();
$(this)
.closest('.wrapper-creationrights')
.toggleClass('is-shown')
.find('.ui-toggle-control')
.toggleClass('current');
});
var reloadPage = function () {
location.reload();
};
var showError = function () {
$('#request-coursecreator-submit')
.toggleClass('has-error')
.find('.label')
.text('Sorry, there was error with your request');
$('#request-coursecreator-submit')
.find('.icon-cog')
.toggleClass('icon-spin');
};
$('#request-coursecreator').ajaxForm({
error: showError,
success: reloadPage
});
$('#request-coursecreator-submit').click(function(event){
$(this)
.toggleClass('is-disabled is-submitting')
.find('.label')
.text('Submitting Your Request');
});
};
});
define(['jquery.cookie', 'utility'], function() {
'use strict';
return function (homepageURL) {
function postJSON(url, data, callback) {
$.ajax({
type:'POST',
url: url,
dataType: 'json',
data: data,
success: callback,
headers : {'X-CSRFToken':$.cookie('csrftoken')}
});
}
$('form#login_form').submit(function(event) {
event.preventDefault();
var submit_data = $('#login_form').serialize();
postJSON('/login_post', submit_data, function(json) {
if(json.success) {
var next = /next=([^&]*)/g.exec(decodeURIComponent(window.location.search));
if (next && next.length > 1 && !isExternal(next[1])) {
location.href = next[1];
} else {
location.href = homepageURL;
}
} else if($('#login_error').length === 0) {
$('#login_form').prepend(
'<div id="login_error" class="message message-status error">' +
json.value +
'</span></div>'
);
$('#login_error').addClass('is-shown');
} else {
$('#login_error')
.stop()
.addClass('is-shown')
.html(json.value);
}
});
});
};
});
define(['jquery', 'underscore', 'gettext', 'js/views/feedback_prompt'], function($, _, gettext, PromptView) {
'use strict';
return function (staffEmails, tplUserURL) {
var unknownErrorMessage = gettext('Unknown'),
$createUserForm = $('#create-user-form'),
$createUserFormWrapper = $createUserForm.closest('.wrapper-create-user'),
$cancelButton;
$createUserForm.bind('submit', function(event) {
event.preventDefault();
var email = $('#user-email-input').val().trim(),
url, msg;
if(!email) {
msg = new PromptView.Error({
title: gettext('A valid email address is required'),
message: gettext('You must enter a valid email address in order to add a new team member'),
actions: {
primary: {
text: gettext('Return and add email address'),
click: function(view) {
view.hide();
$('#user-email-input').focus();
}
}
}
});
msg.show();
}
if(_.contains(staffEmails, email)) {
msg = new PromptView.Warning({
title: gettext('Already a course team member'),
message: _.template(
gettext("{email} is already on the “{course}” team. If you're trying to add a new member, please double-check the email address you provided."), {
email: email,
course: course.escape('name')
}, {interpolate: /\{(.+?)\}/g}
),
actions: {
primary: {
text: gettext('Return to team listing'),
click: function(view) {
view.hide();
$('#user-email-input').focus();
}
}
}
});
msg.show();
}
url = tplUserURL.replace('@@EMAIL@@', $('#user-email-input').val().trim());
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
notifyOnError: false,
data: JSON.stringify({role: 'staff'}),
success: function(data) {location.reload();},
error: function(jqXHR, textStatus, errorThrown) {
var message, prompt;
try {
message = JSON.parse(jqXHR.responseText).error || unknownErrorMessage;
} catch (e) {
message = unknownErrorMessage;
}
prompt = new PromptView.Error({
title: gettext('Error adding user'),
message: message,
actions: {
primary: {
text: gettext('OK'),
click: function(view) {
view.hide();
$('#user-email-input').focus();
}
}
}
});
prompt.show();
}
});
});
$cancelButton = $createUserForm.find('.action-cancel');
$cancelButton.bind('click', function(event) {
event.preventDefault();
$('.create-user-button').toggleClass('is-disabled');
$createUserFormWrapper.toggleClass('is-shown');
$('#user-email-input').val('');
});
$('.create-user-button').bind('click', function(event) {
event.preventDefault();
$('.create-user-button').toggleClass('is-disabled');
$createUserFormWrapper.toggleClass('is-shown');
$createUserForm.find('#user-email-input').focus();
});
$('body').bind('keyup', function(event) {
if(event.which == 27) {
$cancelButton.click();
}
});
$('.remove-user').click(function() {
var email = $(this).data('id'),
msg = new PromptView.Warning({
title: gettext('Are you sure?'),
message: _.template(gettext('Are you sure you want to delete {email} from the course team for “{course}”?'), {email: email, course: course.get('name')}, {interpolate: /\{(.+?)\}/g}),
actions: {
primary: {
text: gettext('Delete'),
click: function(view) {
var url = tplUserURL.replace('@@EMAIL@@', email);
view.hide();
$.ajax({
url: url,
type: 'DELETE',
dataType: 'json',
contentType: 'application/json',
notifyOnError: false,
success: function(data) {location.reload();},
error: function(jqXHR, textStatus, errorThrown) {
var message;
try {
message = JSON.parse(jqXHR.responseText).error || unknownErrorMessage;
} catch (e) {
message = unknownErrorMessage;
}
var prompt = new PromptView.Error({
title: gettext('Error removing user'),
message: message,
actions: {
primary: {
text: gettext('OK'),
click: function(view) {
view.hide();
}
}
}
});
prompt.show();
}
});
}
},
secondary: {
text: gettext('Cancel'),
click: function(view) {
view.hide();
}
}
}
});
msg.show();
});
$('.toggle-admin-role').click(function(event) {
event.preventDefault();
var type, url, role;
if($(this).hasClass('add-admin-role')) {
role = 'instructor';
} else {
role = 'staff';
}
url = $(this).closest('li[data-url]').data('url');
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
notifyOnError: false,
data: JSON.stringify({role: role}),
success: function(data) {location.reload();},
error: function(jqXHR, textStatus, errorThrown) {
var message, prompt;
try {
message = JSON.parse(jqXHR.responseText).error || unknownErrorMessage;
} catch (e) {
message = unknownErrorMessage;
}
prompt = new PromptView.Error({
title: gettext("There was an error changing the user's role"),
message: message,
actions: {
primary: {
text: gettext('Try Again'),
click: function(view) {
view.hide();
}
}
}
});
prompt.show();
}
});
});
};
});
define([
'js/views/pages/course_outline', 'js/models/xblock_outline_info'
], function(CourseOutlinePage, XBlockOutlineInfo) {
'use strict';
return function (XBlockOutlineInfoJson, initialStateJson) {
var courseXBlock = new XBlockOutlineInfo(XBlockOutlineInfoJson, {parse: true}),
view = new CourseOutlinePage({
el: $('#content'),
model: courseXBlock,
initialState: initialStateJson
});
view.render();
};
});
define(['jquery', 'jquery.cookie'], function($) {
'use strict';
return function () {
$('form :input')
.focus(function() {
$('label[for="' + this.id + '"]').addClass('is-focused');
})
.blur(function() {
$('label').removeClass('is-focused');
});
$('form#register_form').submit(function(event) {
event.preventDefault();
var submit_data = $('#register_form').serialize();
$.ajax({
url: '/create_account',
type: 'POST',
dataType: 'json',
headers: {'X-CSRFToken': $.cookie('csrftoken')},
notifyOnError: false,
data: submit_data,
success: function(json) {
location.href = '/course/';
},
error: function(jqXHR, textStatus, errorThrown) {
var json = $.parseJSON(jqXHR.responseText);
$('#register_error').html(json.value).stop().addClass('is-shown');
}
});
});
};
});
define([
'jquery', 'js/models/settings/course_details', 'js/views/settings/main'
], function($, CourseDetailsModel, MainView) {
'use strict';
return function (detailsUrl) {
var model;
// highlighting labels when fields are focused in
$('form :input')
.focus(function() {
$('label[for="' + this.id + '"]').addClass('is-focused');
})
.blur(function() {
$('label').removeClass('is-focused');
});
model = new CourseDetailsModel();
model.urlRoot = detailsUrl;
model.fetch({
success: function(model) {
var editor = new MainView({
el: $('.settings-details'),
model: model
});
editor.render();
},
reset: true
});
};
});
define([
'jquery', 'gettext', 'js/models/settings/advanced', 'js/views/settings/advanced'
], function($, gettext, AdvancedSettingsModel, AdvancedSettingsView) {
'use strict';
return function (advancedDict, advancedSettingsUrl) {
var advancedModel, editor;
$('form :input')
.focus(function() {
$('label[for="' + this.id + '"]').addClass('is-focused');
})
.blur(function() {
$('label').removeClass('is-focused');
});
// proactively populate advanced b/c it has the filtered list and doesn't really follow the model pattern
advancedModel = new AdvancedSettingsModel(advancedDict, {parse: true});
advancedModel.url = advancedSettingsUrl;
editor = new AdvancedSettingsView({
el: $('.settings-advanced'),
model: advancedModel
});
editor.render();
$('#deprecated-settings').click(function() {
var wrapperDeprecatedSetting = $('.wrapper-deprecated-setting'),
deprecatedSettingsLabel = $('.deprecated-settings-label');
if ($(this).is(':checked')) {
wrapperDeprecatedSetting.addClass('is-set');
deprecatedSettingsLabel.text(gettext('Hide Deprecated Settings'));
editor.render_deprecated = true;
}
else {
wrapperDeprecatedSetting.removeClass('is-set');
deprecatedSettingsLabel.text(gettext('Show Deprecated Settings'));
editor.render_deprecated = false;
}
editor.render();
});
};
});
define([
'jquery', 'js/views/settings/grading', 'js/models/settings/course_grading_policy'
], function($, GradingView, CourseGradingPolicyModel) {
'use strict';
return function (courseDetails, gradingUrl) {
var model, editor;
$('form :input')
.focus(function() {
$('label[for="' + this.id + '"]').addClass('is-focused');
})
.blur(function() {
$('label').removeClass('is-focused');
});
model = new CourseGradingPolicyModel(courseDetails,{parse:true});
model.urlRoot = gradingUrl;
editor = new GradingView({
el: $('.settings-grading'),
model : model
});
editor.render();
};
});
define([
'gettext', 'js/models/section', 'js/collections/textbook', 'js/views/list_textbooks'
], function(gettext, Section, TextbookCollection, ListTextbooksView) {
'use strict';
return function (textbooksJson) {
var textbooks = new TextbookCollection(textbooksJson, {parse: true}),
tbView = new ListTextbooksView({collection: textbooks});
$('.content-primary').append(tbView.render().el);
$('.nav-actions .new-button').click(function(event) {
tbView.addOne(event);
});
$(window).on('beforeunload', function() {
var dirty = textbooks.find(function(textbook) { return textbook.isDirty(); });
if(dirty) {
return gettext('You have unsaved changes. Do you really want to leave this page?');
}
});
};
});
require.config({
// NOTE: baseUrl has been previously set in cms/static/templates/base.html
waitSeconds: 60,
paths: {
"domReady": "js/vendor/domReady",
"gettext": "/i18n",
"mustache": "js/vendor/mustache",
"codemirror": "js/vendor/codemirror-compressed",
"codemirror/stex": "js/vendor/CodeMirror/stex",
"jquery": "js/vendor/jquery.min",
"jquery.ui": "js/vendor/jquery-ui.min",
"jquery.form": "js/vendor/jquery.form",
"jquery.markitup": "js/vendor/markitup/jquery.markitup",
"jquery.leanModal": "js/vendor/jquery.leanModal.min",
"jquery.ajaxQueue": "js/vendor/jquery.ajaxQueue",
"jquery.smoothScroll": "js/vendor/jquery.smooth-scroll.min",
"jquery.timepicker": "js/vendor/timepicker/jquery.timepicker",
"jquery.cookie": "js/vendor/jquery.cookie",
"jquery.qtip": "js/vendor/jquery.qtip.min",
"jquery.scrollTo": "js/vendor/jquery.scrollTo-1.4.2-min",
"jquery.flot": "js/vendor/flot/jquery.flot.min",
"jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
"jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
"jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill",
"jquery.immediateDescendents": "coffee/src/jquery.immediateDescendents",
"datepair": "js/vendor/timepicker/datepair",
"date": "js/vendor/date",
"underscore": "js/vendor/underscore-min",
"underscore.string": "js/vendor/underscore.string.min",
"backbone": "js/vendor/backbone-min",
"backbone.associations": "js/vendor/backbone-associations-min",
"backbone.paginator": "js/vendor/backbone.paginator.min",
"tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min",
"jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min",
"xmodule": "/xmodule/xmodule",
"xblock": "coffee/src/xblock",
"utility": "js/src/utility",
"accessibility": "js/src/accessibility_tools",
"draggabilly": "js/vendor/draggabilly.pkgd",
"URI": "js/vendor/URI.min",
"ieshim": "js/src/ie_shim",
"tooltip_manager": "js/src/tooltip_manager",
// Files needed for Annotations feature
"annotator": "js/vendor/ova/annotator-full",
"annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth",
"video.dev": "js/vendor/ova/video.dev",
"vjs.youtube": 'js/vendor/ova/vjs.youtube',
"rangeslider": 'js/vendor/ova/rangeslider',
"share-annotator": 'js/vendor/ova/share-annotator',
"richText-annotator": 'js/vendor/ova/richText-annotator',
"reply-annotator": 'js/vendor/ova/reply-annotator',
"grouping-annotator": 'js/vendor/ova/grouping-annotator',
"tags-annotator": 'js/vendor/ova/tags-annotator',
"diacritic-annotator": 'js/vendor/ova/diacritic-annotator',
"flagging-annotator": 'js/vendor/ova/flagging-annotator',
"jquery-Watch": 'js/vendor/ova/jquery-Watch',
"openseadragon": 'js/vendor/ova/openseadragon',
"osda": 'js/vendor/ova/OpenSeaDragonAnnotation',
"ova": 'js/vendor/ova/ova',
"catch": 'js/vendor/ova/catch/js/catch',
"handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2',
// end of Annotation tool files
// externally hosted files
"tender": [
"//edxedge.tenderapp.com/tender_widget",
// if tender fails to load, fallback on a local file
// so that require doesn't fall over
"js/src/tender_fallback"
],
"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"youtube": [
// youtube URL does not end in ".js". We add "?noext" to the path so
// that require.js adds the ".js" to the query component of the URL,
// and leaves the path component intact.
"//www.youtube.com/player_api?noext",
// if youtube fails to load, fallback on a local file
// so that require doesn't fall over
"js/src/youtube_fallback"
]
},
shim: {
"gettext": {
exports: "gettext"
},
"date": {
exports: "Date"
},
"jquery.ui": {
deps: ["jquery"],
exports: "jQuery.ui"
},
"jquery.form": {
deps: ["jquery"],
exports: "jQuery.fn.ajaxForm"
},
"jquery.markitup": {
deps: ["jquery"],
exports: "jQuery.fn.markitup"
},
"jquery.leanmodal": {
deps: ["jquery"],
exports: "jQuery.fn.leanModal"
},
"jquery.ajaxQueue": {
deps: ["jquery"],
exports: "jQuery.fn.ajaxQueue"
},
"jquery.smoothScroll": {
deps: ["jquery"],
exports: "jQuery.fn.smoothScroll"
},
"jquery.cookie": {
deps: ["jquery"],
exports: "jQuery.fn.cookie"
},
"jquery.qtip": {
deps: ["jquery"],
exports: "jQuery.fn.qtip"
},
"jquery.scrollTo": {
deps: ["jquery"],
exports: "jQuery.fn.scrollTo",
},
"jquery.flot": {
deps: ["jquery"],
exports: "jQuery.fn.plot"
},
"jquery.fileupload": {
deps: ["jquery.iframe-transport"],
exports: "jQuery.fn.fileupload"
},
"jquery.inputnumber": {
deps: ["jquery"],
exports: "jQuery.fn.inputNumber"
},
"jquery.tinymce": {
deps: ["jquery", "tinymce"],
exports: "jQuery.fn.tinymce"
},
"datepair": {
deps: ["jquery.ui", "jquery.timepicker"]
},
"underscore": {
exports: "_"
},
"backbone": {
deps: ["underscore", "jquery"],
exports: "Backbone"
},
"backbone.associations": {
deps: ["backbone"],
exports: "Backbone.Associations"
},
"backbone.paginator": {
deps: ["backbone"],
exports: "Backbone.Paginator"
},
"tender": {
exports: 'Tender'
},
"youtube": {
exports: "YT"
},
"codemirror": {
exports: "CodeMirror"
},
"codemirror/stex": {
deps: ["codemirror"]
},
"tinymce": {
exports: "tinymce"
},
"mathjax": {
exports: "MathJax",
init: function() {
MathJax.Hub.Config({
tex2jax: {
inlineMath: [
["\\(","\\)"],
['[mathjaxinline]','[/mathjaxinline]']
],
displayMath: [
["\\[","\\]"],
['[mathjax]','[/mathjax]']
]
}
});
MathJax.Hub.Configured();
}
},
"URI": {
exports: "URI"
},
"tooltip_manager": {
deps: ["jquery", "underscore"]
},
"jquery.immediateDescendents": {
deps: ["jquery"]
},
"xblock/core": {
exports: "XBlock",
deps: ["jquery", "jquery.immediateDescendents"]
},
"xblock/runtime.v1": {
exports: "XBlock",
deps: ["xblock/core"]
},
"coffee/src/main": {
deps: ["coffee/src/ajax_prefix"]
},
"coffee/src/logger": {
exports: "Logger",
deps: ["coffee/src/ajax_prefix"]
},
// the following are all needed for annotation tools
"video.dev": {
exports:"videojs"
},
"vjs.youtube": {
deps: ["video.dev"]
},
"rangeslider": {
deps: ["video.dev"]
},
"annotator": {
exports: "Annotator"
},
"annotator-harvardx":{
deps: ["annotator"]
},
"share-annotator": {
deps: ["annotator"]
},
"richText-annotator": {
deps: ["annotator", "tinymce"]
},
"reply-annotator": {
deps: ["annotator"]
},
"tags-annotator": {
deps: ["annotator"]
},
"diacritic-annotator": {
deps: ["annotator"]
},
"flagging-annotator": {
deps: ["annotator"]
},
"grouping-annotator": {
deps: ["annotator"]
},
"ova":{
exports: "ova",
deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator", "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator", "jquery-Watch", "catch", "handlebars", "URI"]
},
"osda":{
exports: "osda",
deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator", "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars", "URI"]
},
// end of annotation tool files
}
});
......@@ -17,18 +17,10 @@
% endfor
</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["jquery", "js/collections/asset", "js/views/assets", "jquery.fileupload"],
function($, AssetCollection, AssetsView) {
var assets = new AssetCollection();
assets.url = "${asset_callback_url}";
var assetsView = new AssetsView({collection: assets, el: $('.assets-wrapper')});
assetsView.render();
}); // end of require()
</script>
<%block name="requirejs">
require(["js/factories/asset_index"], function (AssetIndexFactory) {
AssetIndexFactory("${asset_callback_url}");
});
</%block>
<%block name="content">
......@@ -72,9 +64,9 @@
</div>
<div class="bit">
<h3 class="title-3">${_("Using File URLs")}</h3>
<p>${_("Use the {em_start}Embed URL{em_end} value to link to the file or image from a component, a course update, or a course handout.").format(em_start='<strong>', em_end="</strong>")}</p>
<p>${_("Use the {em_start}External URL{em_end} value to reference the file or image only from outside of your course.").format(em_start='<strong>', em_end="</strong>")}</p>
<p>${_("Click in the Embed URL or External URL column to select the value, then copy it.")}</p>
</div>
......
......@@ -17,20 +17,10 @@
% endfor
</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["domReady!", "jquery", "js/collections/checklist", "js/views/checklist"],
function(doc, $, ChecklistCollection, ChecklistView) {
var checklistCollection = new ChecklistCollection();
checklistCollection.url = "${handler_url}";
var editor = new ChecklistView({
el: $('.course-checklists'),
collection: checklistCollection
});
checklistCollection.fetch({reset: true});
});
</script>
<%block name="requirejs">
require(["js/factories/checklists"], function (ChecklistsFactory) {
ChecklistsFactory("${handler_url}");
});
</%block>
......
......@@ -34,28 +34,13 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
</%block>
<%block name="jsextra">
<script type='text/javascript'>
require(["domReady!", "jquery", "js/models/xblock_info", "js/views/pages/container",
"js/collections/component_template", "xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function(doc, $, XBlockInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
var templates = new ComponentTemplates(${component_templates | n}, {parse: true});
var mainXBlockInfo = new XBlockInfo(${json.dumps(xblock_info) | n}, {parse: true});
var isUnitPage = ${json.dumps(is_unit_page)}
xmoduleLoader.done(function () {
var view = new ContainerPage({
el: $('#content'),
model: mainXBlockInfo,
action: "${action}",
templates: templates,
isUnitPage: isUnitPage
});
view.render();
});
});
</script>
<%block name="requirejs">
require(["js/factories/container"], function(ContainerFactory) {
ContainerFactory(
${component_templates | n}, ${json.dumps(xblock_info) | n},
"${action}", ${json.dumps(is_unit_page)}
);
});
</%block>
<%block name="content">
......
......@@ -8,15 +8,14 @@
<%block name="bodyclass">is-signedin view-course-create view-course-create-rerun</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["domReady!", "jquery", "jquery.form", "js/views/course_rerun"], function(doc, $) {
});
</script>
<script type="text/javascript">
var source_course_key = "${source_course_key | escapejs}"
</script>
<script type="text/javascript">
var source_course_key = "${source_course_key | escapejs}";
</script>
</%block>
<%block name="requirejs">
require(["js/factories/course_create_rerun"], function (CourseCreateRerunFactory) {
CourseCreateRerunFactory();
});
</%block>
<%block name="content">
......
......@@ -19,30 +19,10 @@
% endfor
</%block>
<%block name="jsextra">
<script type="text/javascript" charset="utf-8">
require(["domReady!", "jquery", "js/collections/course_update", "js/models/module_info", "js/models/course_info", "js/views/course_info_edit"],
function(doc, $, CourseUpdateCollection, ModuleInfoModel, CourseInfoModel, CourseInfoEditView) {
var course_updates = new CourseUpdateCollection();
course_updates.url = '${updates_url}';
course_updates.fetch({reset: true});
var course_handouts = new ModuleInfoModel({
id: '${handouts_locator | escapejs}'
});
var editor = new CourseInfoEditView({
el: $('.main-wrapper'),
model : new CourseInfoModel({
updates : course_updates,
base_asset_url : '${base_asset_url}',
handouts : course_handouts
})
});
editor.render();
});
</script>
<%block name="requirejs">
require(["js/factories/course_info"], function(CourseInfoFactory) {
CourseInfoFactory("${updates_url}", "${handouts_locator | escapejs}", "${base_asset_url}");
});
</%block>
<%block name="content">
......
......@@ -12,19 +12,10 @@ from contentstore.utils import reverse_usage_url
<%namespace name='static' file='static_content.html'/>
<%block name="jsextra">
<script type="text/javascript">
require(["domReady!", "jquery", "js/views/pages/course_outline", "js/models/xblock_outline_info"],
function(doc, $, CourseOutlinePage, XBlockOutlineInfo) {
var courseXBlock = new XBlockOutlineInfo(${json.dumps(course_structure) | n}, { parse: true });
var view = new CourseOutlinePage({
el: $('#content'),
model: courseXBlock,
initialState: ${json.dumps(initial_state) | n}
});
view.render();
});
</script>
<%block name="requirejs">
require(["js/factories/outline"], function (OutlineFactory) {
OutlineFactory(${json.dumps(course_structure) | n}, ${json.dumps(initial_state) | n});
});
</%block>
<%block name="header_extras">
......
......@@ -18,25 +18,10 @@
% endfor
</%block>
<%block name="jsextra">
<script type='text/javascript'>
require(["js/models/explicit_url", "coffee/src/views/tabs",
"xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function (TabsModel, TabsEditView, xmoduleLoader) {
xmoduleLoader.done(function () {
var model = new TabsModel({
id: "${context_course.location | escapejs}",
explicit_url: "${reverse('contentstore.views.tabs_handler', kwargs={'course_key_string': context_course.id})}"
});
new TabsEditView({
el: $('.tab-list'),
model: model,
mast: $('.wrapper-mast')
});
});
});
</script>
<%block name="requirejs">
require(["js/factories/edit_tabs"], function (EditTabsFactory) {
EditTabsFactory("${context_course.location | escapejs}", "${reverse('contentstore.views.tabs_handler', kwargs={'course_key_string': context_course.id})}");
});
</%block>
<%block name="content">
......
......@@ -9,71 +9,17 @@
<%block name="title">${_("Course Export")}</%block>
<%block name="bodyclass">is-signedin course tools view-export</%block>
<%block name="jsextra">
% if in_err:
<script type='text/javascript'>
var hasUnit = ${json.dumps(bool(unit))},
editUnitUrl = "${edit_unit_url or ""}",
courseHomeUrl = "${course_home_url or ""}",
errMsg = ${json.dumps(raw_err_msg or "")};
require(["domReady!", "gettext", "js/views/feedback_prompt"], function(doc, gettext, PromptView) {
var dialog;
if(hasUnit) {
dialog = new PromptView({
title: gettext('There has been an error while exporting.'),
message: gettext("There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages."),
intent: "error",
actions: {
primary: {
text: gettext('Correct failed component'),
click: function(view) {
view.hide();
document.location = editUnitUrl;
}
},
secondary: {
text: gettext('Return to Export'),
click: function(view) {
view.hide();
}
}
}
});
} else {
var msg = "<p>" + gettext("There has been a failure to export your course to XML. Unfortunately, we do not have specific enough information to assist you in identifying the failed component. It is recommended that you inspect your courseware to identify any components in error and try again.") + "</p><p>" + gettext("The raw error message is:") + "</p>" + errMsg;
dialog = new PromptView({
title: gettext('There has been an error with your export.'),
message: msg,
intent: "error",
actions: {
primary: {
text: gettext('Yes, take me to the main course page'),
click: function(view) {
view.hide();
document.location = courseHomeUrl;
}
},
secondary: {
text: gettext('Cancel'),
click: function(view) {
view.hide();
}
}
}
});
}
// The CSS animation for the dialog relies on the 'js' class
// being on the body. This happens after this JavaScript is executed,
// causing a "bouncing" of the dialog after it is initially shown.
// As a workaround, add this class first.
$('body').addClass('js');
dialog.show();
});
</script>
%endif
<%block name="requirejs">
% if in_err:
var hasUnit = ${json.dumps(bool(unit))},
editUnitUrl = "${edit_unit_url or ""}",
courseHomeUrl = "${course_home_url or ""}",
errMsg = ${json.dumps(raw_err_msg or "")};
require(["js/factories/export"], function(ExportFactory) {
ExportFactory(hasUnit, editUnitUrl, courseHomeUrl, errMsg);
});
%endif
</%block>
<%block name="content">
......
......@@ -18,22 +18,12 @@
% endfor
</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["domReady!", "js/collections/group_configuration", "js/views/pages/group_configurations"],
function(doc, GroupConfigurationCollection, GroupConfigurationsPage) {
% if configurations is not None:
var collection = new GroupConfigurationCollection(${json.dumps(configurations)}, { parse: true });
collection.url = "${group_configuration_url}";
collection.outlineUrl = "${course_outline_url}";
new GroupConfigurationsPage({
el: $('#content'),
collection: collection
}).render();
% endif
});
</script>
<%block name="requirejs">
% if configurations is not None:
require(["js/factories/group_configurations"], function(GroupConfigurationsFactory) {
GroupConfigurationsFactory(${json.dumps(configurations)}, "${group_configuration_url}", "${course_outline_url}");
});
% endif
</%block>
<%block name="content">
......
......@@ -147,127 +147,8 @@
</div>
</%block>
<%block name="jsextra">
<script>
require(
["js/views/import", "jquery", "gettext", "jquery.fileupload", "jquery.cookie"],
function(CourseImport, $, gettext) {
var file;
var bar = $('.progress-bar');
var fill = $('.progress-fill');
var percent = $('.percent');
var status = $('#status');
var statusBlock = $('.status-block');
var submitBtn = $('.submit-button');
var chooseBtn = $('.choose-file-button');
var allStats = $('#status-infos');
var feedbackUrl = "${import_status_url}";
var defaults = [
"${_("There was an error during the upload process.")}\n",
"${_("There was an error while unpacking the file.")}\n",
"${_("There was an error while verifying the file you submitted.")}\n",
"${_("There was an error while importing the new course to our database.")}\n"
];
// Display the status of last file upload on page load
var lastfileupload = $.cookie('lastfileupload');
if (lastfileupload){
CourseImport.getAndStartUploadFeedback(feedbackUrl.replace('fillerName', lastfileupload), lastfileupload);
}
$('#fileupload').fileupload({
dataType: 'json',
type: 'POST',
maxChunkSize: 20 * 1000000, // 20 MB
autoUpload: false,
add: function(e, data) {
CourseImport.clearImportDisplay();
submitBtn.unbind('click');
file = data.files[0];
if (file.name.match(/tar\.gz$/)) {
submitBtn.click(function(e){
$.cookie('lastfileupload', file.name);
e.preventDefault();
submitBtn.hide();
CourseImport.startUploadFeedback();
data.submit().complete(function(result, textStatus, xhr) {
window.onbeforeunload = null;
if (xhr.status != 200) {
try{
var serverMsg = $.parseJSON(result.responseText);
} catch (e) {
return;
}
var serverMsg = $.parseJSON(result.responseText);
var errMsg = serverMsg.hasOwnProperty("ErrMsg") ? serverMsg.ErrMsg : "" ;
if (serverMsg.hasOwnProperty("Stage")) {
var stage = Math.abs(serverMsg.Stage);
CourseImport.stageError(stage, defaults[stage] + errMsg);
}
else {
alert("${_("Your import has failed.")}\n\n" + errMsg);
}
chooseBtn.html("${_("Choose new file")}").show();
bar.hide();
}
CourseImport.stopGetStatus = true;
chooseBtn.html("${_("Choose new file")}").show();
bar.hide();
});
});
} else {
data.files = [];
}
},
progressall: function(e, data){
var doneAt;
var percentInt = data.loaded / data.total * 100
var percentVal = parseInt(percentInt, 10) + "%";
// Firefox makes ProgressEvent.loaded equal ProgressEvent.total only
// after receiving a response from the server (see Mozilla bug 637002),
// so for Firefox we jump the gun a little.
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
doneAt = 95;
} else {
doneAt = 99;
}
if (percentInt >= doneAt) {
bar.hide();
// Start feedback with delay so that current stage of import properly updates in session
setTimeout(
function() { CourseImport.startServerFeedback(feedbackUrl.replace('fillerName', file.name)) },
3000
);
} else {
bar.show();
fill.width(percentVal);
fill.html(percentVal);
}
},
done: function(e, data){
bar.hide();
window.onbeforeunload = null;
CourseImport.displayFinishedImport();
},
start: function(e) {
window.onbeforeunload = function() {
return "${_("Your import is in progress; navigating away will abort it.")}";
}
},
sequentialUploads: true,
notifyOnError: false
});
}); // end define()
</script>
<%block name="requirejs">
require(["js/factories/import"], function(ImportFactory) {
ImportFactory("${import_status_url}");
});
</%block>
......@@ -5,31 +5,10 @@
<%block name="title">${_("My Courses")}</%block>
<%block name="bodyclass">is-signedin index view-dashboard</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["domReady!", "jquery", "jquery.form", "js/index"], function(doc, $) {
// showing/hiding creation rights UI
$('.show-creationrights').click(function(e){
(e).preventDefault();
$(this).closest('.wrapper-creationrights').toggleClass('is-shown').find('.ui-toggle-control').toggleClass('current');
});
var reloadPage = function () {
location.reload();
};
var showError = function () {
$('#request-coursecreator-submit').toggleClass('has-error').find('.label').text('Sorry, there was error with your request');
$('#request-coursecreator-submit').find('.icon-cog').toggleClass('icon-spin');
};
$('#request-coursecreator').ajaxForm({error: showError, success: reloadPage});
$('#request-coursecreator-submit').click(function(e){
$(this).toggleClass('is-disabled is-submitting').find('.label').text('Submitting Your Request');
});
});
</script>
<%block name="requirejs">
require(["js/factories/index"], function (IndexFactory) {
IndexFactory();
});
</%block>
<%block name="content">
......
......@@ -57,42 +57,8 @@ from django.utils.translation import ugettext as _
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["jquery", "jquery.cookie", "utility"], function($) {
function postJSON(url, data, callback) {
$.ajax({type:'POST',
url: url,
dataType: 'json',
data: data,
success: callback,
headers : {'X-CSRFToken':$.cookie('csrftoken')}
});
}
$('form#login_form').submit(function(e) {
e.preventDefault();
var submit_data = $('#login_form').serialize();
postJSON('/login_post',
submit_data,
function(json) {
if(json.success) {
var next = /next=([^&]*)/g.exec(decodeURIComponent(window.location.search));
if (next && next.length > 1 && !isExternal(next[1])) {
location.href = next[1];
}
else location.href = "${reverse('homepage')}";
} else if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error" class="message message-status error">' + json.value + '</span></div>');
$('#login_error').addClass('is-shown');
} else {
$('#login_error').stop().addClass('is-shown');
$('#login_error').html(json.value);
}
}
);
<%block name="requirejs">
require(["js/factories/login"], function(LoginFactory) {
LoginFactory("${reverse('homepage')}");
});
});
</script>
</%block>
......@@ -161,216 +161,8 @@
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["jquery", "underscore", "gettext", "js/views/feedback_prompt"],
function($, _, gettext, PromptView) {
var staffEmails = ${json.dumps([user.email for user in staff])};
var tplUserURL = "${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': unicode(context_course.id), 'email': '@@EMAIL@@'})}"
var unknownErrorMessage = gettext("Unknown");
$(document).ready(function() {
var $createUserForm = $('#create-user-form');
var $createUserFormWrapper = $createUserForm.closest('.wrapper-create-user');
$createUserForm.bind('submit', function(e) {
e.preventDefault();
var email = $('#user-email-input').val().trim();
if(!email) {
var msg = new PromptView.Error({
title: gettext("A valid email address is required"),
message: gettext("You must enter a valid email address in order to add a new team member"),
actions: {
primary: {
text: gettext("Return and add email address"),
click: function(view) {
view.hide();
$("#user-email-input").focus();
}
}
}
})
msg.show();
return;
}
if(_.contains(staffEmails, email)) {
var msg = new PromptView.Warning({
title: gettext("Already a course team member"),
message: _.template(gettext("{email} is already on the “{course}” team. If you're trying to add a new member, please double-check the email address you provided."), {email: email, course: course.escape('name')}, {interpolate: /\{(.+?)\}/g}),
actions: {
primary: {
text: gettext("Return to team listing"),
click: function(view) {
view.hide();
$("#user-email-input").focus();
}
}
}
})
msg.show();
return;
}
var url = tplUserURL.replace("@@EMAIL@@", $('#user-email-input').val().trim())
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({
role: 'staff',
}),
success: function(data) {
location.reload();
},
notifyOnError: false,
error: function(jqXHR, textStatus, errorThrown) {
var message;
try {
message = JSON.parse(jqXHR.responseText).error || unknownErrorMessage;
} catch (e) {
message = unknownErrorMessage
}
var prompt = new PromptView.Error({
title: gettext("Error adding user"),
message: message,
actions: {
primary: {
text: gettext("OK"),
click: function(view) {
view.hide();
$("#user-email-input").focus()
}
}
}
})
prompt.show();
}
});
});
var $cancelButton = $createUserForm.find('.action-cancel');
$cancelButton.bind('click', function(e) {
e.preventDefault();
$('.create-user-button').toggleClass('is-disabled');
$createUserFormWrapper.toggleClass('is-shown');
$('#user-email-input').val('');
});
$('.create-user-button').bind('click', function(e) {
e.preventDefault();
$('.create-user-button').toggleClass('is-disabled');
$createUserFormWrapper.toggleClass('is-shown');
$createUserForm.find('#user-email-input').focus();
});
$('body').bind('keyup', function(e) {
if(e.which == 27) {
$cancelButton.click();
}
});
$('.remove-user').click(function() {
var email = $(this).data('id');
var msg = new PromptView.Warning({
title: gettext("Are you sure?"),
message: _.template(gettext("Are you sure you want to delete {email} from the course team for “{course}”?"), {email: email, course: course.get('name')}, {interpolate: /\{(.+?)\}/g}),
actions: {
primary: {
text: gettext("Delete"),
click: function(view) {
view.hide();
var url = tplUserURL.replace("@@EMAIL@@", email)
$.ajax({
url: url,
type: 'DELETE',
dataType: 'json',
contentType: 'application/json',
success: function(data) {
location.reload();
},
notifyOnError: false,
error: function(jqXHR, textStatus, errorThrown) {
var message;
try {
message = JSON.parse(jqXHR.responseText).error || unknownErrorMessage;
} catch (e) {
message = unknownErrorMessage;
}
var prompt = new PromptView.Error({
title: gettext("Error removing user"),
message: message,
actions: {
primary: {
text: gettext("OK"),
click: function(view) {
view.hide();
}
}
}
})
prompt.show();
}
});
}
},
secondary: {
text: gettext("Cancel"),
click: function(view) {
view.hide();
}
}
}
});
msg.show();
});
$(".toggle-admin-role").click(function(e) {
e.preventDefault()
var type;
if($(this).hasClass("add-admin-role")) {
role = 'instructor';
} else {
role = 'staff';
}
var url = $(this).closest("li[data-url]").data("url");
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({
role: role
}),
success: function(data) {
location.reload();
},
notifyOnError: false,
error: function(jqXHR, textStatus, errorThrown) {
var message;
try {
message = JSON.parse(jqXHR.responseText).error || unknownErrorMessage;
} catch (e) {
message = unknownErrorMessage;
}
var prompt = new PromptView.Error({
title: gettext("There was an error changing the user's role"),
message: message,
actions: {
primary: {
text: gettext("Try Again"),
click: function(view) {
view.hide();
}
}
}
})
prompt.show();
}
})
})
<%block name="requirejs">
require(["js/factories/manage_users"], function(ManageUsersFactory) {
ManageUsersFactory(${json.dumps([user.email for user in staff])}, "${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': unicode(context_course.id), 'email': '@@EMAIL@@'})}");
});
});
</script>
</%block>
......@@ -99,35 +99,8 @@
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript">
require(["jquery", "jquery.cookie"], function($) {
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
$('form#register_form').submit(function(e) {
e.preventDefault();
var submit_data = $('#register_form').serialize();
$.ajax({
url: '/create_account',
type: 'POST',
dataType: 'json',
data: submit_data,
headers: {'X-CSRFToken': $.cookie('csrftoken')},
success: function(json) {
location.href = "/course/";
},
error: function(jqXHR, textStatus, errorThrown) {
json = $.parseJSON(jqXHR.responseText);
$('#register_error').html(json.value).stop().addClass('is-shown');
},
notifyOnError: false
<%block name="requirejs">
require(["js/factories/register"], function (RegisterFactory) {
RegisterFactory();
});
});
});
</script>
</%block>
......@@ -25,30 +25,13 @@
window.CMS = window.CMS || {};
CMS.URL = CMS.URL || {};
CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/settings/main"],
function(doc, $, CourseDetailsModel, MainView) {
// highlighting labels when fields are focused in
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
var model = new CourseDetailsModel();
model.urlRoot = '${details_url}';
model.fetch({
success: function(model) {
var editor = new MainView({
el: $('.settings-details'),
model: model
});
editor.render();
},
reset: true
});
});
</script>
</%block>
<%block name="requirejs">
require(["js/factories/settings"], function(SettingsFactory) {
SettingsFactory("${details_url}");
});
</%block>
<%block name="content">
<div class="wrapper-mast wrapper">
......
......@@ -9,53 +9,18 @@
<%block name="title">${_("Advanced Settings")}</%block>
<%block name="bodyclass">is-signedin course advanced view-settings</%block>
<%block name="jsextra">
<%block name="header_extras">
% for template_name in ["advanced_entry", "basic-modal", "modal-button", "validation-error-modal"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
% endfor
</%block>
<script type="text/javascript">
require(["domReady!", "jquery", "gettext", "js/models/settings/advanced", "js/views/settings/advanced"],
function(doc, $, gettext, AdvancedSettingsModel, AdvancedSettingsView) {
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
// proactively populate advanced b/c it has the filtered list and doesn't really follow the model pattern
var advancedModel = new AdvancedSettingsModel(${advanced_dict | n}, {parse: true});
advancedModel.url = "${advanced_settings_url}";
var editor = new AdvancedSettingsView({
el: $('.settings-advanced'),
model: advancedModel
});
editor.render();
$("#deprecated-settings").click(function() {
var $this = $(this);
var wrapperDeprecatedSetting = $('.wrapper-deprecated-setting');
var deprecatedSettingsLabel = $('.deprecated-settings-label');
if ($this.is(':checked')) {
wrapperDeprecatedSetting.addClass('is-set');
deprecatedSettingsLabel.text('${escapejs(_('Hide Deprecated Settings'))}');
editor.render_deprecated = true;
}
else {
wrapperDeprecatedSetting.removeClass('is-set');
deprecatedSettingsLabel.text('${escapejs(_('Show Deprecated Settings'))}');
editor.render_deprecated = false;
}
editor.render();
});
});
</script>
<%block name="requirejs">
require(["js/factories/settings_advanced"], function(SettingsAdvancedFactory) {
SettingsAdvancedFactory(${advanced_dict | n}, "${advanced_settings_url}");
});
</%block>
<%block name="content">
......
......@@ -19,24 +19,11 @@
<%block name="jsextra">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script type="text/javascript">
require(["domReady!", "jquery", "js/views/settings/grading", "js/models/settings/course_grading_policy"], function(doc, $, GradingView, CourseGradingPolicyModel) {
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
var model = new CourseGradingPolicyModel(${course_details|n},{parse:true});
model.urlRoot = '${grading_url}';
var editor = new GradingView({
el: $('.settings-grading'),
model : model
</%block>
<%block name="requirejs">
require(["js/factories/settings_graders"], function(SettingsGradersFactory) {
SettingsGradersFactory(${course_details|n}, "${grading_url}");
});
editor.render();
});
</script>
</%block>
<%block name="content">
......
......@@ -22,26 +22,13 @@ CMS.URL = CMS.URL || {};
CMS.URL.UPLOAD_ASSET = "${upload_asset_url}"
CMS.URL.TEXTBOOKS = "${textbook_url}"
CMS.URL.LMS_BASE = "${settings.LMS_BASE}"
require(["js/models/section", "js/collections/textbook", "js/views/list_textbooks"],
function(Section, TextbookCollection, ListTextbooksView) {
var textbooks = new TextbookCollection(${json.dumps(textbooks)}, {parse: true});
var tbView = new ListTextbooksView({collection: textbooks});
$(function() {
$(".content-primary").append(tbView.render().el);
$(".nav-actions .new-button").click(function(e) {
tbView.addOne(e);
})
$(window).on("beforeunload", function() {
var dirty = textbooks.find(function(textbook) { return textbook.isDirty(); });
if(dirty) {
return "${_('You have unsaved changes. Do you really want to leave this page?')}";
}
})
})
});
</script>
</%block>
<%block name="requirejs">
require(["js/factories/textbooks"], function(TextbooksFactory) {
TextbooksFactory(${json.dumps(textbooks)});
});
</%block>
<%block name="content">
<div class="wrapper-mast wrapper">
......
......@@ -3,7 +3,7 @@
* @author: Coolite Inc. http://www.coolite.com/
* @date: 2008-05-13
* @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved.
* @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
* @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
* @website: http://www.datejs.com/
*/
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|aft(er)?|from|hence)/i,subtract:/^(\-|bef(ore)?|ago)/i,yesterday:/^yes(terday)?/i,today:/^t(od(ay)?)?/i,tomorrow:/^tom(orrow)?/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^mn|min(ute)?s?/i,hour:/^h(our)?s?/i,week:/^w(eek)?s?/i,month:/^m(onth)?s?/i,day:/^d(ay)?s?/i,year:/^y(ear)?s?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a(?!u|p)|p)/i},timezones:[{name:"UTC",offset:"-000"},{name:"GMT",offset:"-000"},{name:"EST",offset:"-0500"},{name:"EDT",offset:"-0400"},{name:"CST",offset:"-0600"},{name:"CDT",offset:"-0500"},{name:"MST",offset:"-0700"},{name:"MDT",offset:"-0600"},{name:"PST",offset:"-0800"},{name:"PDT",offset:"-0700"}]};
......@@ -44,7 +44,7 @@ f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z"';};}
$P._toString=$P.toString;$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}}
if(typeof $P._toString === 'undefined'){$P._toString=$P.toString;}$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}}
var ord=function(n){switch(n*1){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};return format?format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,function(m){if(m.charAt(0)==="\\"){return m.replace("\\","");}
x.h=x.getHours;switch(m){case"hh":return p(x.h()<13?(x.h()===0?12:x.h()):(x.h()-12));case"h":return x.h()<13?(x.h()===0?12:x.h()):(x.h()-12);case"HH":return p(x.h());case"H":return x.h();case"mm":return p(x.getMinutes());case"m":return x.getMinutes();case"ss":return p(x.getSeconds());case"s":return x.getSeconds();case"yyyy":return p(x.getFullYear(),4);case"yy":return p(x.getFullYear());case"dddd":return $C.dayNames[x.getDay()];case"ddd":return $C.abbreviatedDayNames[x.getDay()];case"dd":return p(x.getDate());case"d":return x.getDate();case"MMMM":return $C.monthNames[x.getMonth()];case"MMM":return $C.abbreviatedMonthNames[x.getMonth()];case"MM":return p((x.getMonth()+1));case"M":return x.getMonth()+1;case"t":return x.h()<12?$C.amDesignator.substring(0,1):$C.pmDesignator.substring(0,1);case"tt":return x.h()<12?$C.amDesignator:$C.pmDesignator;case"S":return ord(x.getDate());default:return m;}}):this._toString();};}());
(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,$N=Number.prototype;$P._orient=+1;$P._nth=null;$P._is=false;$P._same=false;$P._isSecond=false;$N._dateElement="day";$P.next=function(){this._orient=+1;return this;};$D.next=function(){return $D.today().next();};$P.last=$P.prev=$P.previous=function(){this._orient=-1;return this;};$D.last=$D.prev=$D.previous=function(){return $D.today().last();};$P.is=function(){this._is=true;return this;};$P.same=function(){this._same=true;this._isSecond=false;return this;};$P.today=function(){return this.same().day();};$P.weekday=function(){if(this._is){this._is=false;return(!this.is().sat()&&!this.is().sun());}
......
......@@ -2,6 +2,7 @@
Course Advanced Settings page
"""
from bok_choy.promise import EmptyPromise
from .course_page import CoursePage
from .utils import press_the_notification_button, type_in_codemirror, get_codemirror_value
......@@ -13,6 +14,7 @@ MODAL_SELECTOR = ".validation-error-modal-content"
ERROR_ITEM_NAME_SELECTOR = ".error-item-title strong"
ERROR_ITEM_CONTENT_SELECTOR = ".error-item-message"
class AdvancedSettingsPage(CoursePage):
"""
Course Advanced Settings page.
......@@ -21,6 +23,10 @@ class AdvancedSettingsPage(CoursePage):
url_path = "settings/advanced"
def is_browser_on_page(self):
def _is_finished_loading():
return len(self.q(css='.course-advanced-policy-list-item')) > 0
EmptyPromise(_is_finished_loading, 'Finished rendering the advanced policy items.').fulfill()
return self.q(css='body.advanced').present
def wait_for_modal_load(self):
......
......@@ -4,6 +4,7 @@ Utility methods useful for Studio page tests.
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from bok_choy.promise import EmptyPromise
from bok_choy.javascript import js_defined
from ...tests.helpers import disable_animations
......@@ -50,6 +51,7 @@ def wait_for_notification(page):
EmptyPromise(_is_saving_done, 'Notification should have been hidden.', timeout=60).fulfill()
@js_defined('window.jQuery')
def press_the_notification_button(page, name):
# Because the notification uses a CSS transition,
# Selenium will always report it as being visible.
......@@ -101,6 +103,7 @@ def add_advanced_component(page, menu_index, name):
click_css(page, component_css, 0)
@js_defined('window.jQuery')
def type_in_codemirror(page, index, text, find_prefix="$"):
script = """
var cm = {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror;
......@@ -110,6 +113,7 @@ def type_in_codemirror(page, index, text, find_prefix="$"):
page.browser.execute_script(script, str(text))
@js_defined('window.jQuery')
def get_codemirror_value(page, index=0, find_prefix="$"):
return page.browser.execute_script(
"""
......
......@@ -83,6 +83,7 @@ sympy==0.7.1
xmltodict==0.4.1
django-ratelimit-backend==0.6
unicodecsv==0.9.4
django-require==1.0.6
# Used for development operation
watchdog==0.7.1
......
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